diff --git a/.craft.yml b/.craft.yml index 24db28f8ae..b070e46995 100644 --- a/.craft.yml +++ b/.craft.yml @@ -37,9 +37,6 @@ targets: - name: npm id: "@sentry/junior-linear" includeNames: /^sentry-junior-linear-\d.*\.tgz$/ - - name: npm - id: "@sentry/junior-memory" - includeNames: /^sentry-junior-memory-\d.*\.tgz$/ - name: npm id: "@sentry/junior-notion" includeNames: /^sentry-junior-notion-\d.*\.tgz$/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e68dc719a3..73b1438f9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,7 +79,6 @@ jobs: - packages/junior-hex/** - packages/junior-linear/** - packages/junior-maintenance/** - - packages/junior-memory/** - packages/junior-notion/** - packages/junior-octolens/** - packages/junior-sentry/** @@ -263,12 +262,10 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-node-pnpm - - run: pnpm --filter @sentry/junior-memory build - run: pnpm --filter @sentry/junior-github build - run: pnpm --filter @sentry/junior-gocd build - run: pnpm --filter @sentry/junior-sentry build - run: pnpm --filter @sentry/junior-vercel build - - run: pnpm --filter @sentry/junior-memory test - run: pnpm --filter @sentry/junior-github test - run: pnpm --filter @sentry/junior-gocd test - run: pnpm --filter @sentry/junior-sentry test @@ -453,7 +450,6 @@ jobs: pnpm --filter @sentry/junior-gocd pack --pack-destination artifacts pnpm --filter @sentry/junior-hex pack --pack-destination artifacts pnpm --filter @sentry/junior-linear pack --pack-destination artifacts - pnpm --filter @sentry/junior-memory pack --pack-destination artifacts pnpm --filter @sentry/junior-notion pack --pack-destination artifacts pnpm --filter @sentry/junior-octolens pack --pack-destination artifacts pnpm --filter @sentry/junior-maintenance pack --pack-destination artifacts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 11439ef18f..69993e1115 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -135,7 +135,6 @@ This repo uses Craft for manual lockstep npm releases of: - `@sentry/junior-hex` - `@sentry/junior-linear` - `@sentry/junior-maintenance` -- `@sentry/junior-memory` - `@sentry/junior-notion` - `@sentry/junior-octolens` - `@sentry/junior-sentry` diff --git a/README.md b/README.md index 5722932c8b..0ee81e12f6 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,6 @@ Start here: | `@sentry/junior-gocd` | GoCD plugin package for read-only pipeline history through host egress auth | | `@sentry/junior-hex` | Hex plugin package for data warehouse query workflows | | `@sentry/junior-linear` | Linear plugin package for issue workflows | -| `@sentry/junior-memory` | Memory plugin package for long-term Junior memory storage | | `@sentry/junior-notion` | Notion plugin package for page search workflows | | `@sentry/junior-octolens` | Octolens plugin package for social-listening workflows | | `@sentry/junior-maintenance` | Maintenance plugin package for updating and improving Junior apps | diff --git a/TERMINOLOGY.md b/TERMINOLOGY.md index 9d15f6228f..de5447c5f0 100644 --- a/TERMINOLOGY.md +++ b/TERMINOLOGY.md @@ -19,6 +19,11 @@ Canonical words used across Junior's code and documentation. - **Brief**: the durable, versioned record of a Conversation's intent, outcome, decisions, facts, and evidence links. A public Brief stays available after the transcript expires. +- **Memory**: a durable fact that Junior keeps across Conversations. It is + private to one User or public. Core owns Memory storage, recall, and learning. +- **Core feature**: a product capability that core always registers with the + plugin hook contract, such as Memory or Brief generation. It is not a plugin, + and no plugin can use its name. - **Source**: the input that caused work, such as a Slack message, local CLI input, dashboard input, event, scheduled automation, plugin dispatch, or Agent invocation. Every Inbound message has one Source. A Turn stores the diff --git a/apps/example/README.md b/apps/example/README.md index 02f10c661d..89e3fa27c6 100644 --- a/apps/example/README.md +++ b/apps/example/README.md @@ -7,7 +7,7 @@ It demonstrates: - one local skill (`/example-local`) - one plugin-bundled skill (`/example-bundle-help`) - one bundle-only plugin (`app/plugins/example-bundle/plugin.yaml`) with no credential broker config -- installed plugin packages (`@sentry/junior-agent-browser`, `@sentry/junior-datadog`, `@sentry/junior-github`, `@sentry/junior-hex`, `@sentry/junior-linear`, `@sentry/junior-memory`, `@sentry/junior-notion`, `@sentry/junior-sentry`, `@sentry/junior-vercel`) +- installed plugin packages (`@sentry/junior-agent-browser`, `@sentry/junior-datadog`, `@sentry/junior-github`, `@sentry/junior-hex`, `@sentry/junior-linear`, `@sentry/junior-notion`, `@sentry/junior-sentry`, `@sentry/junior-vercel`) ## Run diff --git a/apps/example/package.json b/apps/example/package.json index a7d17be6ce..8e63a11f35 100644 --- a/apps/example/package.json +++ b/apps/example/package.json @@ -3,9 +3,9 @@ "private": true, "type": "module", "scripts": { - "predev": "pnpm --filter @sentry/junior build && pnpm --filter @sentry/junior-dashboard build && pnpm --filter @sentry/junior-memory build", + "predev": "pnpm --filter @sentry/junior build && pnpm --filter @sentry/junior-dashboard build", "dev": "cross-env NODE_OPTIONS=--import=./instrument.mjs nitro dev", - "prebuild": "pnpm --filter @sentry/junior build && pnpm --filter @sentry/junior-dashboard build && pnpm --filter @sentry/junior-memory build", + "prebuild": "pnpm --filter @sentry/junior build && pnpm --filter @sentry/junior-dashboard build", "build": "junior snapshot create && nitro build", "postbuild": "node scripts/check-vercel-output.mjs", "preview": "cross-env NODE_OPTIONS=--import=./instrument.mjs nitro preview", @@ -22,7 +22,6 @@ "@sentry/junior-github": "workspace:*", "@sentry/junior-hex": "workspace:*", "@sentry/junior-linear": "workspace:*", - "@sentry/junior-memory": "workspace:*", "@sentry/junior-notion": "workspace:*", "@sentry/junior-sentry": "workspace:*", "@sentry/junior-vercel": "workspace:*", diff --git a/apps/example/plugins.ts b/apps/example/plugins.ts index a23bb71c52..27708d7a8c 100644 --- a/apps/example/plugins.ts +++ b/apps/example/plugins.ts @@ -1,7 +1,6 @@ import { defineJuniorPlugins } from "@sentry/junior"; import { githubPlugin } from "@sentry/junior-github"; import { linearPlugin } from "@sentry/junior-linear"; -import { memoryPlugin } from "@sentry/junior-memory"; import { sentryPlugin } from "@sentry/junior-sentry"; import { vercelPlugin } from "@sentry/junior-vercel"; @@ -19,7 +18,6 @@ export const plugins = defineJuniorPlugins([ }), "@sentry/junior-hex", linearPlugin(), - memoryPlugin(), "@sentry/junior-notion", sentryPlugin(), vercelPlugin(), diff --git a/ast-grep/rules/no-memory-semantic-regex.yml b/ast-grep/rules/no-memory-semantic-regex.yml index a1c8d50d93..6787f4ffc1 100644 --- a/ast-grep/rules/no-memory-semantic-regex.yml +++ b/ast-grep/rules/no-memory-semantic-regex.yml @@ -3,10 +3,10 @@ language: TypeScript severity: error message: Memory semantic decisions must stay agentic. Do not use regex classifiers in memory tools, policy, extraction, or agent modules. files: - - packages/junior-memory/src/tools.ts - - packages/junior-memory/src/agent.ts - - packages/junior-memory/src/**/*policy*.ts - - packages/junior-memory/src/**/*extraction*.ts + - packages/junior/src/chat/memory/tools.ts + - packages/junior/src/chat/memory/agent.ts + - packages/junior/src/chat/memory/**/*policy*.ts + - packages/junior/src/chat/memory/**/*extraction*.ts rule: any: - pattern: /$R/ diff --git a/package.json b/package.json index 75d0d021d0..f9b2b249bf 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "worktree:setup": "node scripts/worktree.mjs setup", "cloudflare:token": "node scripts/refresh-cloudflare-tunnel-token.mjs", "prepare": "simple-git-hooks", - "lint": "pnpm file-length:check && pnpm migration-metadata:check && pnpm test-architecture:check && pnpm dashboard-style:check && pnpm --filter @sentry/junior tool-annotations:check && pnpm --filter @sentry/junior tool-error-classification:check && pnpm --filter @sentry/junior lint && pnpm --filter @sentry/junior-memory lint && pnpm --filter @sentry/junior-github lint && pnpm --filter @sentry/junior-gocd lint && pnpm --filter @sentry/junior-linear lint && pnpm --filter @sentry/junior-sentry lint && pnpm --filter @sentry/junior-vercel lint && pnpm --filter @sentry/junior-dashboard lint && pnpm ast-grep:lint && pnpm package:lint", + "lint": "pnpm file-length:check && pnpm migration-metadata:check && pnpm test-architecture:check && pnpm dashboard-style:check && pnpm --filter @sentry/junior tool-annotations:check && pnpm --filter @sentry/junior tool-error-classification:check && pnpm --filter @sentry/junior lint && pnpm --filter @sentry/junior-github lint && pnpm --filter @sentry/junior-gocd lint && pnpm --filter @sentry/junior-linear lint && pnpm --filter @sentry/junior-sentry lint && pnpm --filter @sentry/junior-vercel lint && pnpm --filter @sentry/junior-dashboard lint && pnpm ast-grep:lint && pnpm package:lint", "lint:fix": "pnpm --filter @sentry/junior lint:fix", "file-length:check": "node --test scripts/check-file-length.test.mjs && node scripts/check-file-length.mjs", "migration-metadata:check": "node --test scripts/check-drizzle-migrations.test.mjs && node scripts/check-drizzle-migrations.mjs", @@ -21,16 +21,16 @@ "dashboard-style:check": "node --test scripts/check-dashboard-styles.test.mjs && node scripts/check-dashboard-styles.mjs", "ast-grep:lint": "ast-grep scan", "lint-staged": "lint-staged", - "build": "pnpm --filter @sentry/junior build && pnpm --filter @sentry/junior-memory build && pnpm --filter @sentry/junior-github build && pnpm --filter @sentry/junior-gocd build && pnpm --filter @sentry/junior-linear build && pnpm --filter @sentry/junior-sentry build && pnpm --filter @sentry/junior-vercel build && pnpm --filter @sentry/junior-dashboard build", + "build": "pnpm --filter @sentry/junior build && pnpm --filter @sentry/junior-github build && pnpm --filter @sentry/junior-gocd build && pnpm --filter @sentry/junior-linear build && pnpm --filter @sentry/junior-sentry build && pnpm --filter @sentry/junior-vercel build && pnpm --filter @sentry/junior-dashboard build", "build:example": "pnpm --filter @sentry/junior-example build", "docs:dev": "pnpm --filter @sentry/junior-docs dev", "docs:build": "pnpm --filter @sentry/junior-docs build", "docs:check": "pnpm --filter @sentry/junior-docs check", - "package:lint": "for pkg in packages/junior packages/junior-plugin-api packages/junior-memory packages/junior-dashboard packages/junior-github packages/junior-gocd packages/junior-agent-browser packages/junior-amplitude packages/junior-cloudflare packages/junior-datadog packages/junior-hex packages/junior-linear packages/junior-maintenance packages/junior-notion packages/junior-octolens packages/junior-sentry packages/junior-vercel; do pnpm exec publint \"$pkg\" || exit $?; done", + "package:lint": "for pkg in packages/junior packages/junior-plugin-api packages/junior-dashboard packages/junior-github packages/junior-gocd packages/junior-agent-browser packages/junior-amplitude packages/junior-cloudflare packages/junior-datadog packages/junior-hex packages/junior-linear packages/junior-maintenance packages/junior-notion packages/junior-octolens packages/junior-sentry packages/junior-vercel; do pnpm exec publint \"$pkg\" || exit $?; done", "package-types:check": "sh scripts/check-package-types.sh", "release:check": "node scripts/check-release-config.mjs", "start": "pnpm --filter @sentry/junior-example dev", - "test": "pnpm --filter @sentry/junior build && pnpm --filter @sentry/junior-memory build && pnpm --filter @sentry/junior-github build && pnpm --filter @sentry/junior-gocd build && pnpm --filter @sentry/junior-linear build && pnpm --filter @sentry/junior-sentry build && pnpm --filter @sentry/junior-vercel build && pnpm --filter @sentry/junior-dashboard build && pnpm --filter @sentry/junior test && pnpm --filter @sentry/junior-memory test && pnpm --filter @sentry/junior-github test && pnpm --filter @sentry/junior-gocd test && pnpm --filter @sentry/junior-sentry test && pnpm --filter @sentry/junior-vercel test && pnpm --filter @sentry/junior-dashboard test", + "test": "pnpm --filter @sentry/junior build && pnpm --filter @sentry/junior-github build && pnpm --filter @sentry/junior-gocd build && pnpm --filter @sentry/junior-linear build && pnpm --filter @sentry/junior-sentry build && pnpm --filter @sentry/junior-vercel build && pnpm --filter @sentry/junior-dashboard build && pnpm --filter @sentry/junior test && pnpm --filter @sentry/junior-github test && pnpm --filter @sentry/junior-gocd test && pnpm --filter @sentry/junior-sentry test && pnpm --filter @sentry/junior-vercel test && pnpm --filter @sentry/junior-dashboard test", "test:e2e:dashboard": "pnpm --filter @sentry/junior-dashboard build && playwright test -c packages/junior-dashboard/playwright.config.ts", "test:watch": "pnpm --filter @sentry/junior test:watch", "evals": "pnpm --filter @sentry/junior-evals evals", @@ -41,7 +41,7 @@ "evals:record": "pnpm --filter @sentry/junior-evals evals:record", "typecheck": "pnpm -r run typecheck", "skills:check": "pnpm --filter @sentry/junior skills:check", - "test:ci": "pnpm --filter @sentry/junior build && pnpm --filter @sentry/junior-memory build && pnpm --filter @sentry/junior-github build && pnpm --filter @sentry/junior-gocd build && pnpm --filter @sentry/junior-linear build && pnpm --filter @sentry/junior-sentry build && pnpm --filter @sentry/junior-vercel build && pnpm --filter @sentry/junior-dashboard build && pnpm --filter @sentry/junior test:coverage && pnpm --filter @sentry/junior-memory test && pnpm --filter @sentry/junior-github test && pnpm --filter @sentry/junior-gocd test && pnpm --filter @sentry/junior-sentry test && pnpm --filter @sentry/junior-vercel test && pnpm --filter @sentry/junior-dashboard test:coverage" + "test:ci": "pnpm --filter @sentry/junior build && pnpm --filter @sentry/junior-github build && pnpm --filter @sentry/junior-gocd build && pnpm --filter @sentry/junior-linear build && pnpm --filter @sentry/junior-sentry build && pnpm --filter @sentry/junior-vercel build && pnpm --filter @sentry/junior-dashboard build && pnpm --filter @sentry/junior test:coverage && pnpm --filter @sentry/junior-github test && pnpm --filter @sentry/junior-gocd test && pnpm --filter @sentry/junior-sentry test && pnpm --filter @sentry/junior-vercel test && pnpm --filter @sentry/junior-dashboard test:coverage" }, "simple-git-hooks": { "pre-commit": "pnpm lint-staged", diff --git a/packages/docs/astro.config.mjs b/packages/docs/astro.config.mjs index 9aa95f550b..4b14c6f09d 100644 --- a/packages/docs/astro.config.mjs +++ b/packages/docs/astro.config.mjs @@ -14,6 +14,7 @@ export default defineConfig({ "/deploy": "/start-here/deploy-to-vercel", "/deploy/vercel": "/start-here/deploy-to-vercel", "/extend/custom-plugins": "/extend/build-a-plugin", + "/extend/memory-plugin": "/concepts/memory", "/extend/plugins-overview": "/extend", "/extend/amplitude": "/extend/amplitude-plugin", "/extend/cloudflare": "/extend/cloudflare-plugin", @@ -103,6 +104,7 @@ export default defineConfig({ link: "/concepts/watches/", }, { label: "Automations", link: "/concepts/automations/" }, + { label: "Memory", link: "/concepts/memory/" }, { label: "Data & Privacy", link: "/concepts/data-and-privacy/", @@ -135,7 +137,6 @@ export default defineConfig({ label: "Maintenance Plugin", link: "/extend/maintenance-plugin/", }, - { label: "Memory Plugin", link: "/extend/memory-plugin/" }, { label: "Notion Plugin", link: "/extend/notion-plugin/" }, { label: "Sentry Plugin", link: "/extend/sentry-plugin/" }, { label: "Vercel Plugin", link: "/extend/vercel-plugin/" }, diff --git a/packages/docs/src/content/docs/cli/init.md b/packages/docs/src/content/docs/cli/init.md index edffd967e4..a071481533 100644 --- a/packages/docs/src/content/docs/cli/init.md +++ b/packages/docs/src/content/docs/cli/init.md @@ -25,9 +25,9 @@ The command requires exactly one argument: the target directory. The scaffold includes: -- `package.json` with `@sentry/junior`, `@sentry/junior-maintenance`, `@sentry/junior-memory`, `hono`, `nitro`, `typescript`, `jiti`, and `@types/node` +- `package.json` with `@sentry/junior`, `@sentry/junior-maintenance`, `hono`, `nitro`, `typescript`, `jiti`, and `@types/node` - `pnpm-workspace.yaml` with a 24-hour dependency release delay and an immediate exception for the `@sentry/*` package scope -- `plugins.ts` with `@sentry/junior-maintenance` and `memoryPlugin()` enabled +- `plugins.ts` with `@sentry/junior-maintenance` enabled - `server.ts` - `nitro.config.ts` pointing at `./plugins` - `tsconfig.json` extending Nitro's TypeScript config @@ -43,7 +43,7 @@ The scaffold includes: `SOUL.md` sets Junior's default voice, `WORLD.md` holds operational context, and `DESCRIPTION.md` powers the user-facing app description. Add other `app/*.md` files only when you want optional reference material available to the agent at runtime. -The generated `plugins.ts` enables `@sentry/junior-maintenance` and `memoryPlugin()` by default. Maintenance provides the `self-update` skill for keeping Junior packages current, and memory provides long-term recall once you configure Postgres with pgvector. `plugins.ts` is also the place to add other packaged plugins later. +The generated `plugins.ts` enables `@sentry/junior-maintenance` by default. Maintenance provides the `self-update` skill for keeping Junior packages current. Long-term [Memory](/concepts/memory/) is part of `@sentry/junior` and needs no plugin; it activates once you configure Postgres with pgvector. `plugins.ts` is the place to add other packaged plugins later. This gives you the supported app shape needed to run Junior locally, keep the app updated, and continue with plugin or skill setup. diff --git a/packages/docs/src/content/docs/cli/upgrade.md b/packages/docs/src/content/docs/cli/upgrade.md index 0fc0a71d32..84d3692574 100644 --- a/packages/docs/src/content/docs/cli/upgrade.md +++ b/packages/docs/src/content/docs/cli/upgrade.md @@ -26,6 +26,20 @@ The command takes no arguments. It migrates the core Junior schema first, then the schemas owned by enabled plugins. Already-applied migrations are recognized as up to date and are not rerun. +Core migrations enable the `vector` and `btree_gin` Postgres extensions. If the +database cannot enable them, the command stops with a prerequisite error before +it applies a migration. + +## Memory adoption + +Core now owns [Memory](/concepts/memory/) storage. A database that ran the old +`@sentry/junior-memory` plugin migrations keeps its tables and data. The core +Memory migration reads the current schema and applies only the missing legacy +changes. The old plugin migration journal stays unchanged. Remove +`memoryPlugin()` from the plugin set before you run the upgrade, because the +command stops while the plugin set still names the removed package. See +[Upgrade from `@sentry/junior-memory`](/concepts/memory/#upgrade-from-sentryjunior-memory). + ## Upgrade bridge for older databases An existing Junior database without core Drizzle migration history must @@ -58,7 +72,6 @@ An already-current database reports its migrations as existing: Checking database migrations... junior: up to date (8 migrations) junior-github: up to date (4 migrations) - junior-memory: up to date (6 migrations) Database is up to date (18 migrations). ``` diff --git a/packages/docs/src/content/docs/concepts/memory.md b/packages/docs/src/content/docs/concepts/memory.md new file mode 100644 index 0000000000..9b2268912a --- /dev/null +++ b/packages/docs/src/content/docs/concepts/memory.md @@ -0,0 +1,186 @@ +--- +title: Memory +description: Understand and configure Junior's long-term memory storage and recall. +type: conceptual +summary: Memory is built into @sentry/junior. It stores durable facts in Postgres with pgvector and recalls them across conversations. +prerequisites: + - /start-here/quickstart/ +related: + - /reference/config-and-env/ + - /cli/upgrade/ + - /concepts/data-and-privacy/ +--- + +Memory is built into `@sentry/junior`. Every app gets it. You do not install or register a plugin for it. + +Junior stores long-term memories in Postgres and uses the pgvector extension for semantic search. Before each user turn, Junior combines semantic and full-text matches. It includes only the memories that directly help with the current request. Junior also exposes explicit memory tools (remember, list, search, and forget). After a session completes, Junior can learn durable facts from it. + +## Prerequisites + +Junior requires the `vector` (pgvector) and `btree_gin` Postgres extensions on every database. `junior upgrade` enables them. If the database cannot enable them, the upgrade stops with a prerequisite error. Most managed Postgres providers support both extensions, including Neon, Supabase, Railway, and AWS RDS or Aurora PostgreSQL with pgvector enabled. + +Memory stores 1536-dimensional embeddings. It keeps a scope-aware full-text search index and an HNSW cosine index on embeddings for hybrid recall. + +## Configure + +Pass Memory settings to `createApp({ memory })`: + +```ts title="server.ts" +import { createApp } from "@sentry/junior"; + +const app = await createApp({ + memory: { + disableExtraction: true, + }, +}); +``` + +
+modelId + +Model for memory classification, consolidation, and automatic recall relevance. + +- **Define:** `createApp({ memory: { modelId: "anthropic/claude-sonnet-4-5" } })` +- **Default:** The app's default model +- **Required:** No +- **Environment override:** `AI_MEMORY_MODEL`; the `modelId` setting takes precedence + +
+ +
+disableRecall + +Stops automatic prompt recall. The explicit memory tools stay available. + +- **Define:** `createApp({ memory: { disableRecall: true } })` +- **Default:** `false` +- **Required:** No + +
+ +
+disableExtraction + +Stops passive memory extraction from completed sessions. The explicit memory tools stay available. + +- **Define:** `createApp({ memory: { disableExtraction: true } })` +- **Default:** `false` +- **Required:** No + +
+ +
+enabled + +Emergency switch that removes every Memory surface from the app, including tools, recall, extraction, routes, and the **Memories** page. Stored memories stay in the database. + +- **Define:** `createApp({ memory: { enabled: false } })` +- **Default:** `true` +- **Required:** No + +
+ +`disableRecall` and `disableExtraction` are independent. + +### Environment variables + +
+AI_EMBEDDING_MODEL + +Embedding model for vector search. + +- **Define:** Set `AI_EMBEDDING_MODEL` in the deployment environment +- **Default:** `openai/text-embedding-3-small` +- **Required:** No + +The model must produce 1536-dimensional vectors. If you change it after memories exist, flush `junior_memory_embeddings` so that Junior can make new embeddings. The fixed `0.45` cosine distance cutoff for automatic recall is tuned for the default model. + +
+ +Memory uses the same `DATABASE_URL`, `JUNIOR_DATABASE_DRIVER`, and `JUNIOR_SQL_STATEMENT_TIMEOUT_MS` settings as the rest of Junior. See [Config & Env Reference](/reference/config-and-env/). + +## Manage personal memories + +Signed-in users can search, page through, and forget their personal memories +from the top-level **Memories** dashboard page. The page shows viewer-scoped +memory totals, embedding coverage, and history on **Overview**. The separate +**Memories** view provides search and collections for preferences, +automatically learned memories, and explicitly saved memories. Each record +explains whether Junior learned it automatically or saved it because the user +asked. Overview groups the viewer's active memories by type and how they were +added. Forgetting archives the memory so Junior no longer recalls it. + +Junior also exposes authenticated REST resources: + +| Method | Path | Purpose | +| -------- | ---------------------------------------------------- | --------------------------------------------- | +| `GET` | `/api/memory/dashboard` | Read viewer-scoped memory totals and timeline | +| `GET` | `/api/memory/memories` | List memories with `q`, `cursor`, and `limit` | +| `GET` | `/api/memory/memories/:id` | Read one personal memory | +| `DELETE` | `/api/memory/memories/:id` | Forget one personal memory | +| `GET` | `/api/memory/conversations/:conversationId/memories` | List memories captured from one Conversation | + +Personal API tokens can use the read endpoints. Deletion requires an +authenticated dashboard browser session. + +## Run migrations + +After you set `DATABASE_URL`, run the upgrade command: + +```bash +pnpm junior upgrade +``` + +On a fresh database, this enables the `vector` and `btree_gin` extensions and creates the `junior_memory_memories` and `junior_memory_embeddings` tables. + +## Upgrade from `@sentry/junior-memory` + +Earlier releases shipped Memory as the `@sentry/junior-memory` plugin. Core now owns the same tables, data, events, tools, and routes. The upgrade keeps every stored memory. Make these changes in the same deploy that installs the new `@sentry/junior` version: + +1. Stop old Junior workers. +2. Install the new `@sentry/junior` version and uninstall `@sentry/junior-memory`. +3. Remove `memoryPlugin()` from `defineJuniorPlugins(...)`, and remove `@sentry/junior-memory` from `JUNIOR_PLUGIN_PACKAGES` if you set it. Move the `memoryPlugin()` options to `createApp({ memory })`. +4. Import Memory stores, schemas, and types from `@sentry/junior/memory` instead of `@sentry/junior-memory`. +5. Run `junior upgrade`. A core migration adopts the Memory schema. It reads the current schema and applies only the legacy changes that are missing. It does not change a database that already ran every plugin migration. The old plugin migration journal stays as an audit record. +6. Restart workers. + +REST clients must move from `/api/plugins/memory/*` to `/api/memory/*`. The paths below the prefix are unchanged. + +If you must roll back, deploy the previous release with the `@sentry/junior-memory` plugin. Do not reverse the core migration or drop Memory tables. The previous release reads the same tables. + +## Verify + +Confirm that memory storage and recall work end to end. In a Slack conversation where Junior has actor context, ask Junior to store an explicit memory: + +```text +Remember that I prefer concise bullet-point summaries +``` + +Then verify recall by listing memories directly: + +```text +what memories do you have about me? +``` + +Junior should list the stored preference. To confirm cross-conversation recall, start a new conversation as the same actor and ask: + +```text +What do you remember about my preferences? +``` + +Junior should recall the preference without prompting. + +Public Slack channel memories are workspace-visible. Junior can recall a durable fact from a public channel or public-channel thread in another public channel of the same Slack workspace. Private memory belongs to one User. + +## Failure modes + +- **Upgrade error — Junior requires the Postgres extensions vector and btree_gin**: the database cannot enable one of the extensions. Use a provider that supports pgvector, or enable the extensions as a privileged user with `CREATE EXTENSION vector` and `CREATE EXTENSION btree_gin`. Then rerun `junior upgrade`. +- **Startup error — `@sentry/junior-memory` was removed**: the plugin set or `JUNIOR_PLUGIN_PACKAGES` still names the old plugin. Remove it and move its options to `createApp({ memory })`. +- **Startup error — plugin name "memory" is reserved**: another plugin claims the `memory` name. Rename that plugin. +- **Connection errors on non-Neon Postgres**: set `JUNIOR_DATABASE_DRIVER=postgres` for Railway, Supabase, AWS RDS, or self-hosted Postgres. +- **Embedding dimension mismatch**: `AI_EMBEDDING_MODEL` changed after Junior stored memories with a different model. Flush the `junior_memory_embeddings` table so that Junior can make new embeddings. +- **Memories not recalled**: first run `pnpm junior upgrade` against the production database. If migrations are current, the memories may be outside the vector distance cutoff, or they may not directly help with the request. + +## Next step + +Read [Config & Env Reference](/reference/config-and-env/) for the full list of database and model environment variables. diff --git a/packages/docs/src/content/docs/contribute/releasing.md b/packages/docs/src/content/docs/contribute/releasing.md index eafa8419d9..59415da115 100644 --- a/packages/docs/src/content/docs/contribute/releasing.md +++ b/packages/docs/src/content/docs/contribute/releasing.md @@ -23,7 +23,6 @@ Junior uses lockstep package releases for: - `@sentry/junior-hex` - `@sentry/junior-linear` - `@sentry/junior-maintenance` -- `@sentry/junior-memory` - `@sentry/junior-notion` - `@sentry/junior-octolens` - `@sentry/junior-sentry` diff --git a/packages/docs/src/content/docs/extend/index.md b/packages/docs/src/content/docs/extend/index.md index c1999aaa70..1aa30de969 100644 --- a/packages/docs/src/content/docs/extend/index.md +++ b/packages/docs/src/content/docs/extend/index.md @@ -30,7 +30,6 @@ fixed behavior that code must control. | [Hex](/extend/hex-plugin/) | Hex projects and runs | | [Linear](/extend/linear-plugin/) | Issues, projects, and issue webhooks | | [Maintenance](/extend/maintenance-plugin/) | Repository maintenance workflows | -| [Memory](/extend/memory-plugin/) | Long-term scoped memory | | [Notion](/extend/notion-plugin/) | Notion content | | [Scheduler](/extend/scheduler-plugin/) | Durable scheduled automations | | [Sentry](/extend/sentry-plugin/) | Sentry issues, telemetry, and issue webhooks | diff --git a/packages/docs/src/content/docs/extend/memory-plugin.md b/packages/docs/src/content/docs/extend/memory-plugin.md deleted file mode 100644 index bdb3617c42..0000000000 --- a/packages/docs/src/content/docs/extend/memory-plugin.md +++ /dev/null @@ -1,208 +0,0 @@ ---- -title: Memory Plugin -description: Configure the memory plugin for persistent long-term memory storage and recall. -type: tutorial -summary: Set up pgvector-backed memory storage so Junior can recall preferences and context across conversations. -prerequisites: - - /extend/ -related: - - /reference/config-and-env/ - - /start-here/quickstart/ ---- - -The memory plugin uses a Postgres database with the pgvector extension to store and retrieve long-term memories across conversations. Before each user turn, Junior combines semantic and full-text matches and includes only memories that directly help with the current request. The plugin also exposes explicit memory tools (remember, list, search, remove) and passively extracts memories from completed public-channel and local sessions. - -New apps created with `junior init` include `memoryPlugin()` in `plugins.ts` by default. - -## Prerequisites - -Provision a Postgres database with pgvector support before running migrations. The memory plugin migrations create the `vector` and `btree_gin` extensions, store 1536-dimensional embeddings, maintain a scope-aware full-text search index, and create an HNSW cosine index on embeddings for hybrid recall. Most managed Postgres providers — Neon, Supabase, Railway, and AWS RDS/Aurora PostgreSQL with pgvector enabled — support this out of the box. - -## Install - -Install the plugin package alongside `@sentry/junior`: - -```bash -pnpm add @sentry/junior @sentry/junior-memory -``` - -## Runtime setup - -The memory plugin requires a factory function call to register its tools and session hooks. Add `memoryPlugin()` to the plugin set exported from `plugins.ts`: - -```ts title="plugins.ts" -import { defineJuniorPlugins } from "@sentry/junior"; -import { memoryPlugin } from "@sentry/junior-memory"; - -export const plugins = defineJuniorPlugins([memoryPlugin()]); -``` - -Do not register `@sentry/junior-memory` as a bare package-name string. The memory plugin uses `defineJuniorPlugin` with runtime hooks for tool registration and session processing; a bare string skips those hooks and the plugin will not activate its runtime behavior. - -## Config - -Pass plugin options to `memoryPlugin({ ... })` in `plugins.ts`. Set deployment variables in the Junior environment, then redeploy. - -### Plugin options - -
-modelId - -Model used for memory classification, consolidation, and automatic recall relevance. - -- **Define:** `memoryPlugin({ modelId: "anthropic/claude-sonnet-4-5" })` in `plugins.ts` -- **Default:** The app's structured model -- **Required:** No -- **Environment override:** `AI_MEMORY_MODEL`; the plugin option takes precedence - -
- -
-disableRecall - -Disables automatic prompt recall while keeping explicit memory tools available. - -- **Define:** `memoryPlugin({ disableRecall: true })` in `plugins.ts` -- **Default:** `false` -- **Required:** No -- **Environment override:** None - -
- -
-disableExtraction - -Disables passive memory extraction from completed sessions while keeping explicit memory tools available. - -- **Define:** `memoryPlugin({ disableExtraction: true })` in `plugins.ts` -- **Default:** `false` -- **Required:** No -- **Environment override:** None - -
- -### Environment variables - -
-DATABASE_URL - -Postgres connection string for memory storage. - -- **Define:** Set `DATABASE_URL` in the deployment environment -- **Required:** Yes -- **Environment override:** `DATABASE_URL` - -The database must support pgvector. - -
- -
-JUNIOR_DATABASE_DRIVER - -SQL client driver for memory storage. - -- **Define:** Set `JUNIOR_DATABASE_DRIVER` in the deployment environment -- **Default:** `neon`; local URLs automatically use `postgres` -- **Required:** No -- **Environment override:** `JUNIOR_DATABASE_DRIVER` - -Use `postgres` for non-Neon managed Postgres such as Railway, Supabase, AWS RDS, or self-hosted Postgres. - -
- -
-JUNIOR_SQL_STATEMENT_TIMEOUT_MS - -Runtime PostgreSQL statement timeout in milliseconds. - -- **Define:** Set `JUNIOR_SQL_STATEMENT_TIMEOUT_MS` in the deployment environment -- **Default:** `30000`; set `0` to disable -- **Required:** No -- **Environment override:** `JUNIOR_SQL_STATEMENT_TIMEOUT_MS` - -
- -
-AI_EMBEDDING_MODEL - -Embedding model used for vector search. - -- **Define:** Set `AI_EMBEDDING_MODEL` in the deployment environment -- **Default:** `openai/text-embedding-3-small` -- **Required:** No -- **Environment override:** `AI_EMBEDDING_MODEL` - -The model must produce 1536-dimensional vectors. Changing it after memories exist requires flushing `junior_memory_embeddings` so embeddings can be regenerated. Automatic recall's fixed `0.45` cosine distance cutoff is tuned for the default model. - -
- -## Manage personal memories - -Signed-in users can search, page through, and forget their personal memories -from the top-level **Memories** dashboard page. The page shows viewer-scoped -memory totals, embedding coverage, and history on **Overview**. The separate -**Memories** view provides search and collections for preferences, -automatically learned memories, and explicitly saved memories. Each record -explains whether Junior learned it automatically or saved it because the user -asked. Overview groups the viewer's active memories by type and how they were -added. Forgetting archives the memory so Junior no longer recalls it. - -The plugin also exposes authenticated REST resources: - -| Method | Path | Purpose | -| -------- | ---------------------------------- | --------------------------------------------- | -| `GET` | `/api/plugins/memory/dashboard` | Read viewer-scoped memory totals and timeline | -| `GET` | `/api/plugins/memory/memories` | List memories with `q`, `cursor`, and `limit` | -| `GET` | `/api/plugins/memory/memories/:id` | Read one personal memory | -| `DELETE` | `/api/plugins/memory/memories/:id` | Forget one personal memory | - -Personal API tokens can use the read endpoints. Deletion requires an -authenticated dashboard browser session. - -## Run migrations - -After setting `DATABASE_URL`, run the upgrade command to apply the memory plugin schema: - -```bash -pnpm junior upgrade -``` - -On a fresh database, this creates the `vector` and `btree_gin` extensions, the `junior_memory_memories` table, and the `junior_memory_embeddings` table with a `vector(1536)` column plus its HNSW cosine index. - -## Verify - -Confirm memory storage and recall work end to end. In a Slack conversation where Junior has actor context, ask Junior to store an explicit memory: - -```text -Remember that I prefer concise bullet-point summaries -``` - -Then verify recall by listing memories directly: - -```text -what memories do you have about me? -``` - -Junior should list the stored preference. To confirm cross-conversation recall, start a new conversation as the same actor and ask: - -```text -What do you remember about my preferences? -``` - -Junior should recall the preference without prompting. - -Public Slack channel memories are workspace-visible. A durable fact remembered in a public channel or public-channel thread can be recalled from another public channel in the same Slack workspace. Private Slack and local conversation memories remain scoped to their original conversation. - -## Failure modes - -- **Plugin not active after registration**: `@sentry/junior-memory` was registered as a bare string instead of `memoryPlugin()`. Switch to the factory call and redeploy. -- **Migration error — extension "vector" does not exist**: the Postgres database does not have pgvector available. Use a provider that supports pgvector or install it manually with `CREATE EXTENSION vector`. -- **Migration error — extension "btree_gin" does not exist**: the Postgres database does not include the standard `btree_gin` extension. Enable it with your provider or install it manually with `CREATE EXTENSION btree_gin`. -- **`DATABASE_URL` is required**: no database URL is configured. Set it in the deployment environment. -- **Connection errors on non-Neon Postgres**: set `JUNIOR_DATABASE_DRIVER=postgres` for Railway, Supabase, AWS RDS, or self-hosted Postgres. -- **Embedding dimension mismatch**: `AI_EMBEDDING_MODEL` was changed after memories were stored with a different model. Flush the `junior_memory_embeddings` table and re-run migrations to regenerate vectors with the new model. -- **Memories not recalled**: first run `pnpm junior upgrade` against the production database. If migrations are current, the memories may be outside the configured vector distance or may not be directly relevant to the request. - -## Next step - -Read [Config & Env Reference](/reference/config-and-env/) for the full list of database and model environment variables. diff --git a/packages/docs/src/content/docs/reference/config-and-env.md b/packages/docs/src/content/docs/reference/config-and-env.md index 9b92b330cf..e3dabd9725 100644 --- a/packages/docs/src/content/docs/reference/config-and-env.md +++ b/packages/docs/src/content/docs/reference/config-and-env.md @@ -221,7 +221,7 @@ Each profile value may be a model id string or an object with `modelId` and opti Set `profiles` and `defaultProfile` together. Pass auxiliary model ids on the same `createApp()` options object. App config replaces profiles from env settings and overrides auxiliary model env settings. If app config omits both profile options, the deprecated env settings create `standard` and `handoff` profiles with default task-fit descriptions. `AI_MODEL_PROFILES` can add or replace those profiles and may use the same string or object shape. -The memory model belongs to the memory plugin. Set it with `memoryPlugin({ modelId })` in the plugin set. +The memory model belongs to core Memory. Set it with `createApp({ memory: { modelId } })` or `AI_MEMORY_MODEL`. ## Install-wide config defaults diff --git a/packages/docs/src/content/docs/start-here/quickstart.mdx b/packages/docs/src/content/docs/start-here/quickstart.mdx index 8825f61298..23b3f09540 100644 --- a/packages/docs/src/content/docs/start-here/quickstart.mdx +++ b/packages/docs/src/content/docs/start-here/quickstart.mdx @@ -28,7 +28,7 @@ Use the same baseline that the scaffolded CI workflow uses: - Node.js 24 - pnpm -- A Postgres database for Junior SQL records and the default memory plugin +- A Postgres database with the `vector` (pgvector) and `btree_gin` extensions for Junior SQL records and [Memory](/concepts/memory/) - A Redis URL for runtime state, locks, and durable automation records Slack credentials are needed before the bot can reply in Slack. You can scaffold and verify the local health route first, then finish [Slack App Setup](/start-here/slack-app-setup/). @@ -43,7 +43,7 @@ cd my-bot pnpm install ``` -`junior init` creates the app entrypoint, Nitro config, Vercel config, TypeScript config, CI workflow, app context files, local plugin and skill directories, `.env.example`, and a `plugins.ts` with maintenance and memory enabled by default. +`junior init` creates the app entrypoint, Nitro config, Vercel config, TypeScript config, CI workflow, app context files, local plugin and skill directories, `.env.example`, and a `plugins.ts` with maintenance enabled by default. Memory is built into `@sentry/junior`. The generated `app/` files have separate jobs: @@ -65,28 +65,28 @@ node -e "console.log(require('node:crypto').randomBytes(32).toString('base64url' Set these values before running real turns: -| Variable | Required | Purpose | -| ------------------------- | ---------------------- | ------------------------------------------------------------- | -| `SLACK_SIGNING_SECRET` | Yes, for Slack traffic | Verifies Slack requests. | -| `SLACK_BOT_TOKEN` | Yes, for Slack replies | Posts thread replies and calls Slack APIs. | -| `DATABASE_URL` | Yes | Postgres connection string for Junior SQL records and memory. | -| `JUNIOR_DATABASE_DRIVER` | No | SQL client driver: `neon` or `postgres`. | -| `REDIS_URL` | Yes | Runtime state, locks, and durable background automation records. | -| `JUNIOR_SECRET` | Yes | Signs internal resume callbacks and sandbox actor context. | -| `JUNIOR_BOT_NAME` | No | Bot display/config name. | -| `JUNIOR_SLASH_COMMAND` | No | Slack slash command name. Defaults to `/jr`. | -| `AI_MODEL` | No | Deprecated default model setting. | -| `AI_FAST_MODEL` | No | Lightweight routing/classification model override. | -| `AI_HANDOFF_MODEL` | No | Deprecated handoff model setting. | -| `AI_MODEL_PROFILES` | No | Deprecated JSON map of named profiles. | -| `AI_EMBEDDING_MODEL` | No | Embedding model override for plugin vector retrieval. | -| `AI_VISION_MODEL` | No | Enables image understanding when set. | -| `AI_WEB_SEARCH_MODEL` | No | Search model override. | -| `JUNIOR_STATE_KEY_PREFIX` | No | Redis key namespace for this local app/environment. | +| Variable | Required | Purpose | +| ------------------------- | ---------------------- | ---------------------------------------------------------------- | +| `SLACK_SIGNING_SECRET` | Yes, for Slack traffic | Verifies Slack requests. | +| `SLACK_BOT_TOKEN` | Yes, for Slack replies | Posts thread replies and calls Slack APIs. | +| `DATABASE_URL` | Yes | Postgres connection string for Junior SQL records and memory. | +| `JUNIOR_DATABASE_DRIVER` | No | SQL client driver: `neon` or `postgres`. | +| `REDIS_URL` | Yes | Runtime state, locks, and durable background automation records. | +| `JUNIOR_SECRET` | Yes | Signs internal resume callbacks and sandbox actor context. | +| `JUNIOR_BOT_NAME` | No | Bot display/config name. | +| `JUNIOR_SLASH_COMMAND` | No | Slack slash command name. Defaults to `/jr`. | +| `AI_MODEL` | No | Deprecated default model setting. | +| `AI_FAST_MODEL` | No | Lightweight routing/classification model override. | +| `AI_HANDOFF_MODEL` | No | Deprecated handoff model setting. | +| `AI_MODEL_PROFILES` | No | Deprecated JSON map of named profiles. | +| `AI_EMBEDDING_MODEL` | No | Embedding model override for plugin vector retrieval. | +| `AI_VISION_MODEL` | No | Enables image understanding when set. | +| `AI_WEB_SEARCH_MODEL` | No | Search model override. | +| `JUNIOR_STATE_KEY_PREFIX` | No | Redis key namespace for this local app/environment. | See [Config & Environment](/reference/config-and-env/) for the full reference. -If you keep the default memory plugin enabled, use a Postgres database with -pgvector support before running migrations. Local Postgres URLs automatically +Junior requires a Postgres database that can enable the `vector` and +`btree_gin` extensions before you run migrations. Local Postgres URLs automatically use the `postgres` driver; set `JUNIOR_DATABASE_DRIVER=postgres` for other non-Neon Postgres providers. @@ -114,29 +114,27 @@ After you complete [Slack App Setup](/start-here/slack-app-setup/), point Slack ## Add packaged plugins -New apps created with `junior init` already have a `plugins.ts` file with maintenance and memory enabled. To add more packaged plugins, install the packages and add them to the existing plugin set. +New apps created with `junior init` already have a `plugins.ts` file with maintenance enabled. To add more packaged plugins, install the packages and add them to the existing plugin set. For an existing app created without a `plugins.ts`, create one as shown below. Install only the plugins you plan to enable. If you are creating `plugins.ts` -for an existing app, include the default maintenance and memory packages too: +for an existing app, include the default maintenance package too: ```bash -pnpm add @sentry/junior-maintenance @sentry/junior-memory @sentry/junior-agent-browser @sentry/junior-amplitude @sentry/junior-cloudflare @sentry/junior-datadog @sentry/junior-github @sentry/junior-hex @sentry/junior-linear @sentry/junior-notion @sentry/junior-sentry @sentry/junior-vercel +pnpm add @sentry/junior-maintenance @sentry/junior-agent-browser @sentry/junior-amplitude @sentry/junior-cloudflare @sentry/junior-datadog @sentry/junior-github @sentry/junior-hex @sentry/junior-linear @sentry/junior-notion @sentry/junior-sentry @sentry/junior-vercel ``` Add them to the plugin set in `plugins.ts`: ```ts title="plugins.ts" import { defineJuniorPlugins } from "@sentry/junior"; -import { memoryPlugin } from "@sentry/junior-memory"; import { githubPlugin } from "@sentry/junior-github"; import { linearPlugin } from "@sentry/junior-linear"; import { sentryPlugin } from "@sentry/junior-sentry"; import { vercelPlugin } from "@sentry/junior-vercel"; export const plugins = defineJuniorPlugins([ - memoryPlugin(), "@sentry/junior-maintenance", "@sentry/junior-agent-browser", "@sentry/junior-amplitude", diff --git a/packages/docs/src/prompts/junior-agent-runbook.txt b/packages/docs/src/prompts/junior-agent-runbook.txt index aa5d4c2bb9..36f44f1e82 100644 --- a/packages/docs/src/prompts/junior-agent-runbook.txt +++ b/packages/docs/src/prompts/junior-agent-runbook.txt @@ -63,8 +63,8 @@ Success: ## Phase 3 — HUMAN GATE: storage and Vercel project settings -Junior requires Postgres and Redis. The default memory plugin requires Postgres -with pgvector support. Snapshot warmup requires REDIS_URL during the build and +Junior requires Postgres and Redis. Postgres must support the pgvector +(`vector`) and `btree_gin` extensions. Snapshot warmup requires REDIS_URL during the build and Vercel OIDC enabled. Do not guess Marketplace CLI product slugs. Stop and ask the human to open the linked Vercel project and complete: diff --git a/packages/junior-dashboard/e2e/harness.ts b/packages/junior-dashboard/e2e/harness.ts index 477dc6ff87..858b49d956 100644 --- a/packages/junior-dashboard/e2e/harness.ts +++ b/packages/junior-dashboard/e2e/harness.ts @@ -143,32 +143,29 @@ export async function mockDashboardApis( ], }); }); - await page.route( - "**/api/plugins/memory/conversations/*/memories", - async (route) => { - await route.fulfill({ - json: { - memories: [ - { - capturedAt: "2026-08-07T07:01:00.000Z", - content: "Use pnpm for repository commands.", - id: "captured-memory-1", - kind: "preference", - visibility: "private", - }, - { - capturedAt: "2026-08-07T07:01:00.000Z", - content: "Dashboard transcript events should remain expandable.", - id: "captured-memory-2", - kind: "knowledge", - visibility: "public", - }, - ], - }, - }); - }, - ); - await page.route("**/api/plugins/memory/memories/*", async (route) => { + await page.route("**/api/memory/conversations/*/memories", async (route) => { + await route.fulfill({ + json: { + memories: [ + { + capturedAt: "2026-08-07T07:01:00.000Z", + content: "Use pnpm for repository commands.", + id: "captured-memory-1", + kind: "preference", + visibility: "private", + }, + { + capturedAt: "2026-08-07T07:01:00.000Z", + content: "Dashboard transcript events should remain expandable.", + id: "captured-memory-2", + kind: "knowledge", + visibility: "public", + }, + ], + }, + }); + }); + await page.route("**/api/memory/memories/*", async (route) => { if (route.request().method() !== "GET") { await route.fallback(); return; @@ -238,7 +235,7 @@ export async function mockDashboardApis( actions: [ { confirmation: "Forget this memory?", - href: "/api/plugins/memory/memories/memory-1", + href: "/api/memory/memories/memory-1", label: "Forget", method: "DELETE", tone: "danger", @@ -597,7 +594,7 @@ export async function mockDashboardApis( }, }); }); - await page.route("**/api/plugins/memory/dashboard", async (route) => { + await page.route("**/api/memory/dashboard", async (route) => { const start = Date.parse("2026-05-02T00:00:00.000Z"); const days = Array.from({ length: 90 }, (_, index) => { const date = new Date(start + index * 24 * 60 * 60 * 1_000); diff --git a/packages/junior-dashboard/e2e/memories.spec.ts b/packages/junior-dashboard/e2e/memories.spec.ts index 8f02c3258a..84513a9fda 100644 --- a/packages/junior-dashboard/e2e/memories.spec.ts +++ b/packages/junior-dashboard/e2e/memories.spec.ts @@ -158,7 +158,7 @@ test("renders an empty registered plugin page", async ({ page, dashboard }) => { }); test("shows the memory overview error state", async ({ page, dashboard }) => { - await page.route("**/api/plugins/memory/dashboard", async (route) => { + await page.route("**/api/memory/dashboard", async (route) => { await route.fulfill({ json: { error: "Unavailable" }, status: 500 }); }); @@ -177,7 +177,7 @@ test("searches, paginates, and forgets plugin page records", async ({ let forgotMemory = false; let forgetRequests = 0; let dashboardRequestCount = 0; - await page.route("**/api/plugins/memory/dashboard", async (route) => { + await page.route("**/api/memory/dashboard", async (route) => { dashboardRequestCount += 1; await route.fallback(); }); @@ -196,7 +196,7 @@ test("searches, paginates, and forgets plugin page records", async ({ actions: [ { confirmation: "Forget this memory?", - href: "/api/plugins/memory/memories/memory-search", + href: "/api/memory/memories/memory-search", label: "Forget", method: "DELETE", tone: "danger", @@ -221,37 +221,34 @@ test("searches, paginates, and forgets plugin page records", async ({ }, }); }); - await page.route( - "**/api/plugins/memory/memories/memory-search", - async (route) => { - if (route.request().method() === "GET") { - if (forgotMemory) { - await route.fulfill({ - json: { error: "Memory was not found." }, - status: 404, - }); - return; - } + await page.route("**/api/memory/memories/memory-search", async (route) => { + if (route.request().method() === "GET") { + if (forgotMemory) { await route.fulfill({ - json: { - content: "Deploy runbooks live in Notion.", - createdAt: "2026-07-30T12:00:00.000Z", - id: "memory-search", - kind: "knowledge", - observedAt: "2026-07-30T12:00:00.000Z", - origin: "explicit", - sourcePlatform: "slack", - visibility: "private", - }, + json: { error: "Memory was not found." }, + status: 404, }); return; } - forgetRequests += 1; - expect(route.request().method()).toBe("DELETE"); - forgotMemory = true; - await route.fulfill({ status: 204 }); - }, - ); + await route.fulfill({ + json: { + content: "Deploy runbooks live in Notion.", + createdAt: "2026-07-30T12:00:00.000Z", + id: "memory-search", + kind: "knowledge", + observedAt: "2026-07-30T12:00:00.000Z", + origin: "explicit", + sourcePlatform: "slack", + visibility: "private", + }, + }); + return; + } + forgetRequests += 1; + expect(route.request().method()).toBe("DELETE"); + forgotMemory = true; + await route.fulfill({ status: 204 }); + }); await page.goto(`${dashboard.baseURL}/memories/library`); await expect( diff --git a/packages/junior-dashboard/src/client/conversations/ConversationMemories.tsx b/packages/junior-dashboard/src/client/conversations/ConversationMemories.tsx index 6cccaf855e..bc5610ae8b 100644 --- a/packages/junior-dashboard/src/client/conversations/ConversationMemories.tsx +++ b/packages/junior-dashboard/src/client/conversations/ConversationMemories.tsx @@ -27,7 +27,7 @@ export function ConversationMemories(props: { conversationId: string }) { queryFn: ({ signal }) => fetchDashboardJson( conversationMemoryListSchema, - `/api/plugins/memory/conversations/${encodeURIComponent(props.conversationId)}/memories`, + `/api/memory/conversations/${encodeURIComponent(props.conversationId)}/memories`, signal, ), queryKey: ["conversation", props.conversationId, "memories"], diff --git a/packages/junior-dashboard/src/client/pages/memory/memoryDashboard.ts b/packages/junior-dashboard/src/client/pages/memory/memoryDashboard.ts index 848fe93e38..983ce8e85a 100644 --- a/packages/junior-dashboard/src/client/pages/memory/memoryDashboard.ts +++ b/packages/junior-dashboard/src/client/pages/memory/memoryDashboard.ts @@ -57,7 +57,7 @@ export function useMemoryDashboardData() { queryFn: ({ signal }) => fetchDashboardJson( memoryDashboardSchema, - "/api/plugins/memory/dashboard", + "/api/memory/dashboard", signal, ), queryKey: ["dashboard", "plugin-user-page", "memory", "summary"], diff --git a/packages/junior-dashboard/src/client/pages/memory/memoryRecord.ts b/packages/junior-dashboard/src/client/pages/memory/memoryRecord.ts index fc9f4b34f8..aff1e84f4d 100644 --- a/packages/junior-dashboard/src/client/pages/memory/memoryRecord.ts +++ b/packages/junior-dashboard/src/client/pages/memory/memoryRecord.ts @@ -47,7 +47,7 @@ export function memoryPageRecord(memory: MemoryRecord): PluginUserPageRecord { memory.visibility === "public" ? "Forget this memory for everyone?" : "Forget this memory?", - href: `/api/plugins/memory/memories/${encodeURIComponent(memory.id)}`, + href: `/api/memory/memories/${encodeURIComponent(memory.id)}`, label: "Forget", method: "DELETE", tone: "danger", @@ -78,7 +78,7 @@ export function useMemoryRecord(memoryId: string | undefined) { memoryPageRecord( await fetchDashboardJson( memoryRecordSchema, - `/api/plugins/memory/memories/${encodeURIComponent(memoryId!)}`, + `/api/memory/memories/${encodeURIComponent(memoryId!)}`, signal, ), ), diff --git a/packages/junior-dashboard/src/client/pages/user/PluginUserPage.tsx b/packages/junior-dashboard/src/client/pages/user/PluginUserPage.tsx index a551cca038..c223b4acd8 100644 --- a/packages/junior-dashboard/src/client/pages/user/PluginUserPage.tsx +++ b/packages/junior-dashboard/src/client/pages/user/PluginUserPage.tsx @@ -38,8 +38,8 @@ export function PluginUserPageRoute(props: { pages: PluginUserPageLink[] }) { /** * Memory temporarily uses a first-class dashboard renderer because its - * inspection UI exceeds the generic plugin page contract. The memory plugin - * still owns data, authorization, and actions. + * inspection UI exceeds the generic user page contract. Core Memory still + * owns data, authorization, and actions. * * Replace this special case when Junior has a proven custom plugin UI * contract. diff --git a/packages/junior-dashboard/tests/dashboard-plugin-routes.test.ts b/packages/junior-dashboard/tests/dashboard-plugin-routes.test.ts index d367e3e9be..b44637d12d 100644 --- a/packages/junior-dashboard/tests/dashboard-plugin-routes.test.ts +++ b/packages/junior-dashboard/tests/dashboard-plugin-routes.test.ts @@ -30,9 +30,9 @@ function mockDashboardVirtualConfig() { })); } -function memoryRoutes() { +function notesRoutes() { const app = new Hono(); - app.get("/memories", (c) => c.json({ path: c.req.path, ok: true })); + app.get("/notes", (c) => c.json({ path: c.req.path, ok: true })); return app; } @@ -87,7 +87,7 @@ describe("dashboard plugin routes", () => { it("mounts plugin API route apps under the authenticated namespace", async () => { mockDashboardVirtualConfig(); - const pluginApp = memoryRoutes(); + const pluginApp = notesRoutes(); const app = await createApp({ dashboard: { authRequired: false, @@ -96,9 +96,9 @@ describe("dashboard plugin routes", () => { plugins: defineJuniorPlugins([ defineJuniorPlugin({ manifest: { - name: "memory", - displayName: "Memory", - description: "Memory plugin", + name: "notes", + displayName: "Notes", + description: "Notes plugin", }, hooks: { apiRoutes() { @@ -110,11 +110,11 @@ describe("dashboard plugin routes", () => { }); const response = await app.fetch( - new Request("http://localhost/api/plugins/memory/memories"), + new Request("http://localhost/api/plugins/notes/notes"), ); expect(response.status).toBe(200); await expect(response.json()).resolves.toEqual({ - path: "/memories", + path: "/notes", ok: true, }); @@ -123,14 +123,14 @@ describe("dashboard plugin routes", () => { }); it("protects plugin API route apps with dashboard auth", async () => { - const pluginApp = memoryRoutes(); + const pluginApp = notesRoutes(); const unauthenticated = createDashboardApp({ allowedGoogleDomains: ["sentry.io"], auth: auth(null), - pluginRoutes: [{ app: pluginApp, pluginName: "memory" }], + pluginRoutes: [{ app: pluginApp, pluginName: "notes" }], }); const denied = await unauthenticated.fetch( - new Request("http://localhost/api/plugins/memory/memories"), + new Request("http://localhost/api/plugins/notes/notes"), ); expect(denied.status).toBe(401); await expect(denied.json()).resolves.toEqual({ @@ -145,14 +145,14 @@ describe("dashboard plugin routes", () => { emailVerified: true, }, }), - pluginRoutes: [{ app: pluginApp, pluginName: "memory" }], + pluginRoutes: [{ app: pluginApp, pluginName: "notes" }], }); const allowed = await authenticated.fetch( - new Request("http://localhost/api/plugins/memory/memories"), + new Request("http://localhost/api/plugins/notes/notes"), ); expect(allowed.status).toBe(200); await expect(allowed.json()).resolves.toEqual({ - path: "/memories", + path: "/notes", ok: true, }); }); @@ -161,18 +161,18 @@ describe("dashboard plugin routes", () => { authenticatePersonalToken.mockResolvedValue("person@sentry.io"); let dispatched = false; const pluginApp = new Hono(); - pluginApp.delete("/memories/:id", (c) => { + pluginApp.delete("/notes/:id", (c) => { dispatched = true; return c.body(null, 204); }); const app = createDashboardApp({ allowedGoogleDomains: ["sentry.io"], auth: auth(null), - pluginRoutes: [{ app: pluginApp, pluginName: "memory" }], + pluginRoutes: [{ app: pluginApp, pluginName: "notes" }], }); const response = await app.fetch( - new Request("http://localhost/api/plugins/memory/memories/memory-1", { + new Request("http://localhost/api/plugins/notes/notes/note-1", { headers: { authorization: "Bearer jr_pat_valid" }, method: "DELETE", }), @@ -201,13 +201,13 @@ describe("dashboard plugin routes", () => { return Response.json({ ok: true }); }, }, - pluginName: "memory", + pluginName: "notes", }, ], }); const response = await authenticated.fetch( - new Request("http://localhost/api/plugins/memory/memories"), + new Request("http://localhost/api/plugins/notes/notes"), ); expect(response.status).toBe(200); expect(pluginContext).toEqual({ @@ -218,7 +218,7 @@ describe("dashboard plugin routes", () => { name: "Person", }, }, - pluginName: "memory", + pluginName: "notes", }); }); diff --git a/packages/junior-dashboard/tests/memory-record.test.ts b/packages/junior-dashboard/tests/memory-record.test.ts index 5b1c93e38e..0c55a247e4 100644 --- a/packages/junior-dashboard/tests/memory-record.test.ts +++ b/packages/junior-dashboard/tests/memory-record.test.ts @@ -18,7 +18,7 @@ describe("memory permalink record", () => { ).toMatchObject({ actions: [ { - href: "/api/plugins/memory/memories/memory%2F1", + href: "/api/memory/memories/memory%2F1", tone: "danger", }, ], @@ -69,7 +69,7 @@ describe("memory permalink record", () => { actions: [ { confirmation: "Forget this memory for everyone?", - href: "/api/plugins/memory/memories/memory%2Fpublic-1", + href: "/api/memory/memories/memory%2Fpublic-1", tone: "danger", }, ], diff --git a/packages/junior-evals/README.md b/packages/junior-evals/README.md index fd3e771109..61e306c393 100644 --- a/packages/junior-evals/README.md +++ b/packages/junior-evals/README.md @@ -91,6 +91,7 @@ Harness override knobs (in `EvalOverrides`): - `auto_complete_mcp_oauth`: after our app genuinely starts an MCP OAuth flow for the listed providers, the harness immediately completes the fake provider callback. - `auto_complete_oauth`: after our app genuinely starts a generic OAuth flow for the listed providers, the harness immediately completes the fake provider callback. - `credential_providers`: seed normal provider credentials for the listed providers. GitHub uses dummy GitHub App env vars plus an intercepted installation-token exchange; Sentry uses the normal OAuth token store. +- `memory`: run core Memory with production settings, including recall and passive extraction. It is off by default because it adds model calls and a background task to every turn. The Memory evals turn it on through `memoryEvals`. - `mock_image_generation`: stub the image-generation HTTP response with a valid image payload while still exercising the real attachment path. - `plugin_dirs`: load plugin fixtures from eval-local directories without adding workspace packages. - `reply_texts`: override returned reply text per call. diff --git a/packages/junior-evals/evals/memory/actors.eval.ts b/packages/junior-evals/evals/memory/actors.eval.ts index e4aa97178a..47342f5c2b 100644 --- a/packages/junior-evals/evals/memory/actors.eval.ts +++ b/packages/junior-evals/evals/memory/actors.eval.ts @@ -2,15 +2,12 @@ import { expect } from "vitest"; import { describeEval } from "vitest-evals"; import { getDb } from "@/chat/db"; import { readActorIdentity } from "@/chat/plugins/viewer"; -import type { MemoryDb } from "@sentry/junior-memory"; -import { - juniorMemoryEmbeddings, - juniorMemoryMemories, -} from "../../../junior-memory/src/db/schema"; +import type { MemoryDb } from "@/chat/memory/store"; +import { juniorMemoryEmbeddings, juniorMemoryMemories } from "@/db/schema"; import { mention, rubric, - slackEvals, + memoryEvals, steer, threadMessage, } from "../../src/helpers"; @@ -23,9 +20,6 @@ import { * more than one Actor does not store any preference. */ -const memoryPluginOverrides = { - plugin_packages: ["@sentry/junior-memory"], -}; const memoryTeamId = "TEVAL"; const ALICE = { @@ -94,7 +88,7 @@ async function memoriesForActor( ); } -describeEval("Memory with Multiple Actors", slackEvals, (it) => { +describeEval("Memory with Multiple Actors", memoryEvals, (it) => { const bystanderPreferenceThread = { channel_type: "channel", id: "thread-memory-bystander-preference", @@ -107,7 +101,6 @@ describeEval("Memory with Multiple Actors", slackEvals, (it) => { }) => { await clearMemories(); await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention( "Can you help capture takeaways from this retro discussion as we go?", @@ -173,7 +166,6 @@ describeEval("Memory with Multiple Actors", slackEvals, (it) => { }) => { await clearMemories(); await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention( "I prefer status updates with risks listed first. Draft a brief update saying the rollout is paused while we validate the rollback and that the next checkpoint is tomorrow.", @@ -235,7 +227,6 @@ describeEval("Memory with Multiple Actors", slackEvals, (it) => { }) => { await clearMemories(); await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention("Can you recap what has been asked in this thread so far?", { thread: batchedMentionThread, @@ -292,7 +283,6 @@ describeEval("Memory with Multiple Actors", slackEvals, (it) => { }) => { await clearMemories(); await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention( "I prefer recaps as numbered lists, not paragraphs. Can you recap the asks in this thread so far?", @@ -365,7 +355,6 @@ describeEval("Memory with Multiple Actors", slackEvals, (it) => { }) => { await clearMemories(); await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention("Can you help us plan the deploy for the retention fix?", { thread: sharedKnowledgeThread, diff --git a/packages/junior-evals/evals/memory/helpers.ts b/packages/junior-evals/evals/memory/helpers.ts index a18747db98..00daf9d335 100644 --- a/packages/junior-evals/evals/memory/helpers.ts +++ b/packages/junior-evals/evals/memory/helpers.ts @@ -4,17 +4,11 @@ import { getDb, getSqlExecutor } from "@/chat/db"; import { upsertIdentity } from "@/chat/identities/sql"; import { completeText, resolveGatewayModel } from "@/chat/pi/client"; import { createPluginEmbedder } from "@/chat/plugins/model"; -import { createMemoryStore, type MemoryDb } from "@sentry/junior-memory"; +import { createMemoryStore, type MemoryDb } from "@/chat/memory/store"; import { createSlackSource } from "@sentry/junior-plugin-api"; -import { - juniorMemoryEmbeddings, - juniorMemoryMemories, -} from "../../../junior-memory/src/db/schema"; +import { juniorMemoryEmbeddings, juniorMemoryMemories } from "@/db/schema"; import { TEST_USER_ID } from "@junior-tests/fixtures/slack/factories/ids"; -export const memoryPluginOverrides = { - plugin_packages: ["@sentry/junior-memory"], -}; const memoryTeamId = "TEVAL"; const actorUserId = TEST_USER_ID; const memoryJudgeModelId = resolveGatewayModel("openai/gpt-5.4").id; diff --git a/packages/junior-evals/evals/memory/management.eval.ts b/packages/junior-evals/evals/memory/management.eval.ts index 41a2ea4101..a95b1f97fd 100644 --- a/packages/junior-evals/evals/memory/management.eval.ts +++ b/packages/junior-evals/evals/memory/management.eval.ts @@ -1,16 +1,15 @@ import { expect } from "vitest"; import { describeEval, toolCalls } from "vitest-evals"; -import { mention, rubric, slackEvals } from "../../src/helpers"; +import { mention, rubric, memoryEvals } from "../../src/helpers"; import { clearMemories, - memoryPluginOverrides, type MemoryThread, readActiveMemories, readMemories, seedMemory, } from "./helpers"; -describeEval("Memory Management", slackEvals, (it) => { +describeEval("Memory Management", memoryEvals, (it) => { const autoRecallThread = { id: "thread-memory-auto-recall", channel_type: "im", @@ -29,7 +28,6 @@ describeEval("Memory Management", slackEvals, (it) => { }); await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention("How should I structure my next PR summary?", { thread: autoRecallThread, @@ -77,7 +75,6 @@ describeEval("Memory Management", slackEvals, (it) => { }); await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention("For PR summaries, I still want risk notes first.", { thread: passiveDedupeThread, @@ -123,7 +120,6 @@ describeEval("Memory Management", slackEvals, (it) => { }); await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention("Please forget that I prefer terse PR summaries.", { thread: removeThread, diff --git a/packages/junior-evals/evals/memory/shared.eval.ts b/packages/junior-evals/evals/memory/shared.eval.ts index 84eb5c69ff..29d22db976 100644 --- a/packages/junior-evals/evals/memory/shared.eval.ts +++ b/packages/junior-evals/evals/memory/shared.eval.ts @@ -1,18 +1,17 @@ import { expect } from "vitest"; import { describeEval, toolCalls } from "vitest-evals"; -import { mention, rubric, slackEvals } from "../../src/helpers"; +import { mention, rubric, memoryEvals } from "../../src/helpers"; import { clearMemories, expectAssistantMemoryAnswer, expectConversationMemorySemantics, type MemoryThread, - memoryPluginOverrides, readMemories, seedMemory, visibleAssistantText, } from "./helpers"; -describeEval("Shared Memory", slackEvals, (it) => { +describeEval("Shared Memory", memoryEvals, (it) => { const recallRelevanceThread = { channel_type: "channel", id: "thread-memory-recall-relevance", @@ -49,7 +48,6 @@ describeEval("Shared Memory", slackEvals, (it) => { }); await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention( "What do you remember about how CI works in getsentry/junior?", @@ -85,7 +83,6 @@ describeEval("Shared Memory", slackEvals, (it) => { const userText = "Please remember that for flaky webhook triage, inspect delivery headers before retrying the job."; const result = await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention(userText, { thread: explicitTaskProcedureThread, @@ -152,7 +149,6 @@ describeEval("Shared Memory", slackEvals, (it) => { const userText = "For sandbox timeout triage, inspect heartbeat gaps before increasing the timeout."; const result = await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention(userText, { thread: passiveTaskProcedureThread, @@ -219,7 +215,6 @@ describeEval("Shared Memory", slackEvals, (it) => { const userText = "For this team, branch QA runbooks require risk notes before summary notes. Please acknowledge."; const result = await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention(userText, { thread: passiveConversationThread, @@ -284,7 +279,6 @@ describeEval("Shared Memory", slackEvals, (it) => { }) => { await clearMemories(); await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention( "The analytics query says today's signup conversion rate is 8.4%.", @@ -319,7 +313,6 @@ describeEval("Shared Memory", slackEvals, (it) => { }) => { await clearMemories(); await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention("Please remember that David prefers terse PR summaries.", { thread: thirdPartyRememberThread, diff --git a/packages/junior-evals/evals/memory/user.eval.ts b/packages/junior-evals/evals/memory/user.eval.ts index cc05033e4b..eae840f4d0 100644 --- a/packages/junior-evals/evals/memory/user.eval.ts +++ b/packages/junior-evals/evals/memory/user.eval.ts @@ -1,20 +1,19 @@ import { expect } from "vitest"; import { describeEval, toolCalls } from "vitest-evals"; -import { mention, rubric, slackEvals } from "../../src/helpers"; +import { mention, rubric, memoryEvals } from "../../src/helpers"; import { clearMemories, countMemoryEmbeddings, expectActorMemorySemantics, expectAssistantMemoryAnswer, type MemoryThread, - memoryPluginOverrides, readActiveMemories, readMemories, seedMemory, visibleAssistantText, } from "./helpers"; -describeEval("User Memory", slackEvals, (it) => { +describeEval("User Memory", memoryEvals, (it) => { const explicitRememberThread = { id: "thread-memory-explicit-remember", channel_type: "im", @@ -27,7 +26,6 @@ describeEval("User Memory", slackEvals, (it) => { }) => { await clearMemories(); const result = await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention("Please remember that I prefer terse PR summaries.", { thread: explicitRememberThread, @@ -118,7 +116,6 @@ describeEval("User Memory", slackEvals, (it) => { await expect(countMemoryEmbeddings(timezoneRecallThread)).resolves.toBe(50); const result = await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention("what time is it", { thread: timezoneRecallThread, @@ -181,7 +178,6 @@ describeEval("User Memory", slackEvals, (it) => { await clearMemories(); const userText = "ok remember that i think types in python are bad"; const result = await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention(userText, { thread: firstPersonRewrittenThread, @@ -241,7 +237,6 @@ describeEval("User Memory", slackEvals, (it) => { }); await run({ - overrides: memoryPluginOverrides, initialEvents: [ mention( "Please remember that I want risk notes at the start of PR summaries.", diff --git a/packages/junior-evals/package.json b/packages/junior-evals/package.json index eccb3a8948..f016aca8c1 100644 --- a/packages/junior-evals/package.json +++ b/packages/junior-evals/package.json @@ -18,7 +18,6 @@ "@earendil-works/pi-ai": "catalog:", "@sentry/junior": "workspace:*", "@sentry/junior-github": "workspace:*", - "@sentry/junior-memory": "workspace:*", "@sentry/junior-plugin-api": "workspace:*", "@sentry/junior-sentry": "workspace:*", "@sentry/junior-testing": "workspace:*", diff --git a/packages/junior-evals/postgres-global-setup.ts b/packages/junior-evals/postgres-global-setup.ts index 65b2b164cc..453155ba0f 100644 --- a/packages/junior-evals/postgres-global-setup.ts +++ b/packages/junior-evals/postgres-global-setup.ts @@ -1,19 +1,11 @@ -import path from "node:path"; -import { fileURLToPath } from "node:url"; import { cleanupPostgresHarness, setupPostgresTemplate, type PostgresHarnessConfig, } from "@sentry/junior-testing/postgres"; import { migrateSchema } from "@/chat/conversations/sql/migrations"; -import { migratePluginSchemas } from "@/chat/plugins/migrations"; import { createPostgresJuniorSqlExecutor } from "@/db/postgres"; -const workspaceRoot = path.resolve( - path.dirname(fileURLToPath(import.meta.url)), - "../..", -); - interface EvalTestProject { provide(key: "juniorPostgresHarness", value: PostgresHarnessConfig): void; } @@ -44,15 +36,6 @@ export default async function setup( const executor = createPostgresJuniorSqlExecutor({ connectionString }); try { await migrateSchema(executor); - await migratePluginSchemas(executor, [ - { - dir: path.resolve( - workspaceRoot, - "packages/junior-memory/migrations", - ), - pluginName: "memory", - }, - ]); } finally { await executor.close(); } diff --git a/packages/junior-evals/src/behavior-harness.ts b/packages/junior-evals/src/behavior-harness.ts index 078f7dcd7e..e6dfbafad4 100644 --- a/packages/junior-evals/src/behavior-harness.ts +++ b/packages/junior-evals/src/behavior-harness.ts @@ -40,6 +40,8 @@ import { getMcpStoredOAuthCredentials, } from "@/chat/mcp/auth-store"; import { getPlugins, setPlugins } from "@/chat/plugins/agent-hooks"; +import { createCoreFeatures } from "@/chat/app/core-features"; +import { setCoreFeatures } from "@/chat/plugins/core-features"; import { pluginCatalogRuntime } from "@/chat/plugins/catalog-runtime"; import { defineJuniorPlugins, @@ -66,12 +68,12 @@ import { } from "@/chat/scheduled-automations/tasks"; import type { ScheduledAutomation } from "@/chat/scheduled-automations/types"; import { githubPlugin } from "@sentry/junior-github"; -import { memoryPlugin } from "@sentry/junior-memory"; import { sentryPlugin } from "@sentry/junior-sentry"; import { runPluginHeartbeats } from "@/chat/agent-dispatch/heartbeat"; import { runScheduledAutomationHeartbeat } from "@/chat/scheduled-automations/heartbeat"; import { getDispatchRecord } from "@/chat/agent-dispatch/store"; import { ingestEvent } from "@/chat/events/ingest"; +import { EVENT_MAX_WAIT_MS } from "@/chat/task-execution/conversation-turn"; import { createWatch } from "@/chat/events/store"; import { ingestEventAutomations } from "@/chat/event-automations/ingest"; import { createEventAutomation } from "@/chat/event-automations/store"; @@ -132,7 +134,7 @@ interface NormalizedMessage { metadata?: Record; } -const EVAL_PLUGIN_TASK_DRAIN_TIMEOUT_MS = 5_000; +const EVAL_CLEANUP_TIMEOUT_MS = 5_000; interface PendingEvalPluginTask { abort(): void; @@ -163,31 +165,38 @@ async function processEvalPluginTask( /** Drain plugin tasks started by the eval harness before shared state cleanup. */ export async function drainPendingEvalPluginTasks(): Promise { - if (pendingEvalPluginTasks.size === 0) { - return; - } const tasks = [...pendingEvalPluginTasks]; for (const task of tasks) { task.abort(); } + await settleWithin( + tasks.map((task) => task.promise), + "eval plugin task(s) to settle", + ); +} + +/** Wait for aborted harness work, and fail loudly if it ignores the abort. */ +async function settleWithin( + work: Promise[], + description: string, +): Promise { + if (work.length === 0) { + return; + } let timeout: ReturnType | undefined; try { await Promise.race([ - Promise.allSettled(tasks.map((task) => task.promise)), + Promise.allSettled(work), new Promise((_, reject) => { timeout = setTimeout(() => { reject( - new Error( - `Timed out waiting for ${tasks.length} eval plugin task(s) to settle`, - ), + new Error(`Timed out waiting for ${work.length} ${description}`), ); - }, EVAL_PLUGIN_TASK_DRAIN_TIMEOUT_MS); + }, EVAL_CLEANUP_TIMEOUT_MS); }), ]); } finally { - if (timeout) { - clearTimeout(timeout); - } + clearTimeout(timeout); } } @@ -350,6 +359,8 @@ export interface EvalOverrides { credential_providers?: Array<"github" | "sentry">; expired_oauth_tokens?: string[]; github_events?: boolean; + /** Run core Memory with production settings. Off by default. */ + memory?: boolean; mock_image_generation?: boolean; plugin_dirs?: string[]; plugin_packages?: string[]; @@ -728,33 +739,6 @@ function snapshotEnv(keys: readonly string[]): EnvSnapshot { }; } -function abortReason(signal: AbortSignal): Error { - return signal.reason instanceof Error - ? signal.reason - : new Error("Eval reply aborted"); -} - -async function raceWithAbort( - signal: AbortSignal, - operation: () => Promise, -): Promise { - if (signal.aborted) { - throw abortReason(signal); - } - let removeAbortListener = () => {}; - const abortPromise = new Promise((_, reject) => { - const handleAbort = () => reject(abortReason(signal)); - signal.addEventListener("abort", handleAbort, { once: true }); - removeAbortListener = () => - signal.removeEventListener("abort", handleAbort); - }); - try { - return await Promise.race([operation(), abortPromise]); - } finally { - removeAbortListener(); - } -} - function ensureHarnessBaseUrl(): void { process.env.JUNIOR_BASE_URL ??= DEFAULT_EVAL_BASE_URL; } @@ -1548,7 +1532,6 @@ function runtimePluginsForScenario( ...(packages.has("@sentry/junior-github") ? [githubPlugin({ appPermissions: { deployments: "read" } })] : []), - ...(packages.has("@sentry/junior-memory") ? [memoryPlugin()] : []), ...(packages.has("@sentry/junior-sentry") ? [sentryPlugin()] : []), ]; } @@ -1940,63 +1923,63 @@ function buildRuntimeServices( ...(signal ? [signal] : []), AbortSignal.timeout(replyTimeoutMs), ]); - const outcome = await raceWithAbort(replySignal, () => - executeAgentRun( - { - ...runRequest, - signal: replySignal, - deadlineAtMs: Math.min( - runRequest.deadlineAtMs ?? Number.POSITIVE_INFINITY, - Date.now() + replyTimeoutMs, - ), - environment: { - ...runRequest.environment, - attachmentStorage: - runRequest.environment?.attachmentStorage ?? - attachmentStorage, - ...(env.configuredSkillDirs.length > 0 - ? { skillDirs: env.configuredSkillDirs } - : {}), - toolOverrides, - }, - onEvent: async (event) => { - await runRequest.onEvent?.(event); - if (event.type === "tool_started") { - const evalInvocation = toEvalToolInvocation({ - params: event.params, - toolCallId: event.toolCallId, - toolName: event.toolName, - }); - observations.toolInvocations.push(evalInvocation); - pendingToolInvocations.push(evalInvocation); - return; - } - if (event.type !== "tool_finished") { - return; - } - const result = event.report; - const pendingIndex = pendingToolInvocations.findIndex( - (candidate) => candidate.toolCallId === result.toolCallId, - ); - if (pendingIndex === -1) { - return; - } - const [invocation] = pendingToolInvocations.splice( - pendingIndex, - 1, - ); - invocation.completed = true; - invocation.ok = result.ok; - if (result.error) { - invocation.error = result.error; - } - if (result.result !== undefined) { - invocation.result = result.result; - } - }, + // Await the run itself: it stops on replySignal, so an aborted turn + // cannot keep running after the scenario tears down shared state. + const outcome = await executeAgentRun( + { + ...runRequest, + signal: replySignal, + deadlineAtMs: Math.min( + runRequest.deadlineAtMs ?? Number.POSITIVE_INFINITY, + Date.now() + replyTimeoutMs, + ), + environment: { + ...runRequest.environment, + attachmentStorage: + runRequest.environment?.attachmentStorage ?? + attachmentStorage, + ...(env.configuredSkillDirs.length > 0 + ? { skillDirs: env.configuredSkillDirs } + : {}), + toolOverrides, }, - scriptedStream?.stream, - ), + onEvent: async (event) => { + await runRequest.onEvent?.(event); + if (event.type === "tool_started") { + const evalInvocation = toEvalToolInvocation({ + params: event.params, + toolCallId: event.toolCallId, + toolName: event.toolName, + }); + observations.toolInvocations.push(evalInvocation); + pendingToolInvocations.push(evalInvocation); + return; + } + if (event.type !== "tool_finished") { + return; + } + const result = event.report; + const pendingIndex = pendingToolInvocations.findIndex( + (candidate) => candidate.toolCallId === result.toolCallId, + ); + if (pendingIndex === -1) { + return; + } + const [invocation] = pendingToolInvocations.splice( + pendingIndex, + 1, + ); + invocation.completed = true; + invocation.ok = result.ok; + if (result.error) { + invocation.error = result.error; + } + if (result.result !== undefined) { + invocation.result = result.result; + } + }, + }, + scriptedStream?.stream, ); const usage = outcome.status === "completed" @@ -2294,7 +2277,9 @@ async function processEvents(args: { const runGitHubWebhook = async (event: GitHubWebhookEvent): Promise => { const { thread } = await getThreadRecord(event.thread); - const nowMs = Date.now(); + // Deliver the event after its burst window closed, so the Turn runs now + // instead of waiting out the debounce. Integration tests own the window. + const nowMs = Date.now() - EVENT_MAX_WAIT_MS; await createWatch( { conversationId: thread.id, @@ -2328,7 +2313,9 @@ async function processEvents(args: { const runEvent = async (event: EventFixture): Promise => { const { thread } = await getThreadRecord(event.thread); - const nowMs = Date.now(); + // Deliver the event after its burst window closed, so the Turn runs now + // instead of waiting out the debounce. Integration tests own the window. + const nowMs = Date.now() - EVENT_MAX_WAIT_MS; const destination = createEvalDestination(thread); await getConversationStore().recordActivity({ conversationId: thread.id, @@ -2642,20 +2629,58 @@ function collectResults( // Main orchestrator // --------------------------------------------------------------------------- +const activeEvalScenarios = new Set>(); + +/** + * Run one eval scenario and track it until its teardown finishes. + * + * Vitest aborts the scenario signal when a test times out and then moves on + * without waiting. The eval setup waits for the aborted scenario here, so its + * teardown never overlaps the next test. + */ export async function runEvalScenario( scenario: EvalScenario, options: EvalScenarioRunOptions = {}, +): Promise { + const run = executeEvalScenario(scenario, options); + activeEvalScenarios.add(run); + try { + return await run; + } finally { + activeEvalScenarios.delete(run); + } +} + +/** Wait for scenarios left by a timed-out test to stop and tear down. */ +export async function settleActiveEvalScenarios(): Promise { + await settleWithin( + [...activeEvalScenarios], + "aborted eval scenario(s) to tear down", + ); +} + +async function executeEvalScenario( + scenario: EvalScenario, + options: EvalScenarioRunOptions, ): Promise { const logRecords = options.logRecords ?? []; const runtimePlugins = runtimePluginsForScenario(scenario); const env = await setupHarnessEnvironment(scenario, runtimePlugins); let previousPlugins: ReturnType | undefined; + let previousCoreFeatures: ReturnType | undefined; try { const runtimePluginNames = new Set( runtimePlugins.map((plugin) => plugin.manifest.name), ); const currentPlugins = getPlugins(); + // Memory adds recall and extraction model calls to every turn, so only + // scenarios that measure it run it. + previousCoreFeatures = setCoreFeatures( + createCoreFeatures({ + memory: scenario.overrides?.memory ? {} : { enabled: false }, + }), + ); previousPlugins = setPlugins([ ...runtimePlugins, ...currentPlugins.filter( @@ -2775,6 +2800,9 @@ export async function runEvalScenario( if (previousPlugins) { setPlugins(previousPlugins); } + if (previousCoreFeatures) { + setCoreFeatures(previousCoreFeatures); + } await teardownHarnessEnvironment(scenario, env); } } diff --git a/packages/junior-evals/src/helpers.ts b/packages/junior-evals/src/helpers.ts index ba8e15200f..a381b227ab 100644 --- a/packages/junior-evals/src/helpers.ts +++ b/packages/junior-evals/src/helpers.ts @@ -498,7 +498,6 @@ export interface SlackEvalInput { overrides?: EvalOverrides; criteria?: EvalRubric; requireGatewayReady?: boolean; - taskTimeout?: number; requireSandboxReady?: boolean; } @@ -591,14 +590,6 @@ function assertTimeoutBudget(input: SlackEvalInput): void { `Eval reply_timeout_ms ${replyTimeout} exceeds the ${MAX_EVAL_TIMEOUT_MS}ms budget. Use fixtures, mocks, or tool replay instead of raising timeouts.`, ); } - if ( - input.taskTimeout !== undefined && - input.taskTimeout > MAX_EVAL_TIMEOUT_MS - ) { - throw new Error( - `Eval taskTimeout ${input.taskTimeout} exceeds the ${MAX_EVAL_TIMEOUT_MS}ms budget. Use fixtures, mocks, or tool replay instead of raising timeouts.`, - ); - } } /** Builds a structured, maintainer-readable judge rubric for an eval case. */ @@ -699,31 +690,14 @@ export const slackHarness: Harness = { }); try { assertTimeoutBudget(input); - const taskPromise = runEvalScenario( + const result = (await runEvalScenario( { initialEvents: input.initialEvents, events: input.events, overrides: input.overrides, }, { logRecords, signal }, - ) as Promise; - const result = - typeof input.taskTimeout === "number" && input.taskTimeout > 0 - ? await Promise.race([ - taskPromise, - new Promise((_, reject) => - setTimeout( - () => - reject( - new Error( - `Eval harness timed out after ${input.taskTimeout}ms before judge evaluation`, - ), - ), - input.taskTimeout, - ), - ), - ]) - : await taskPromise; + )) as HarnessEvalResult; if (input.requireGatewayReady ?? true) { assertGatewayReady(result); } @@ -790,6 +764,22 @@ export const slackEvals = { judgeThreshold: 0.75, } satisfies DescribeEvalOptions; +/** Slack eval suite options for Memory evals, which run core Memory. */ +export const memoryEvals = { + ...slackEvals, + harness: { + ...slackHarness, + run: async (input, context) => + await slackHarness.run( + { + ...input, + overrides: { memory: true, ...input.overrides }, + }, + context, + ), + }, +} satisfies DescribeEvalOptions; + export interface SlackSideEffects { suggestedPromptCalls: number; threadTitleCalls: number; diff --git a/packages/junior-evals/src/setup.ts b/packages/junior-evals/src/setup.ts index f8a25cf08e..ef4665bde0 100644 --- a/packages/junior-evals/src/setup.ts +++ b/packages/junior-evals/src/setup.ts @@ -38,7 +38,11 @@ beforeEach(async () => { afterEach(async () => { // Keep stateful runtime modules behind the invocation-provided Redis env. - const { drainPendingEvalPluginTasks } = await import("./behavior-harness"); + const { drainPendingEvalPluginTasks, settleActiveEvalScenarios } = + await import("./behavior-harness"); + // A timed-out test leaves its aborted scenario running. Finish its teardown + // before the next test replaces shared runtime state. + await settleActiveEvalScenarios(); await drainPendingEvalPluginTasks(); const { closeDb } = await import("@/chat/db"); await closeDb(); diff --git a/packages/junior-evals/tests/unit/harness/behavior-harness.test.ts b/packages/junior-evals/tests/unit/harness/behavior-harness.test.ts index f4248b5fd7..15bd771efc 100644 --- a/packages/junior-evals/tests/unit/harness/behavior-harness.test.ts +++ b/packages/junior-evals/tests/unit/harness/behavior-harness.test.ts @@ -161,12 +161,12 @@ describe("behavior harness", () => { if (!signal) { throw new Error("missing eval agent signal"); } - await new Promise((resolve) => { - signal.addEventListener("abort", () => resolve(), { + // Like a real agent run, stop with the abort reason. + return await new Promise((_, reject) => { + signal.addEventListener("abort", () => reject(signal.reason), { once: true, }); }); - return {}; }); await runEvalScenario({ diff --git a/packages/junior-evals/tsconfig.json b/packages/junior-evals/tsconfig.json index 87d62a0ec4..ff2ced87c8 100644 --- a/packages/junior-evals/tsconfig.json +++ b/packages/junior-evals/tsconfig.json @@ -13,7 +13,6 @@ "paths": { "@/*": ["../junior/src/*"], "@junior-tests/*": ["../junior/tests/*"], - "@sentry/junior-memory": ["../junior-memory/src/index.ts"], "@sentry/junior-plugin-api": ["../junior-plugin-api/src/index.ts"] } }, diff --git a/packages/junior-evals/vitest.config.ts b/packages/junior-evals/vitest.config.ts index c658732eee..429c111fa3 100644 --- a/packages/junior-evals/vitest.config.ts +++ b/packages/junior-evals/vitest.config.ts @@ -5,7 +5,6 @@ import { loadJuniorTestEnvFiles } from "../junior/tests/fixtures/env"; const juniorPackageRoot = path.resolve(__dirname, "../junior"); const workspaceRoot = path.resolve(__dirname, "../.."); const pluginApiPackageRoot = path.resolve(__dirname, "../junior-plugin-api"); -const memoryPackageRoot = path.resolve(__dirname, "../junior-memory"); loadJuniorTestEnvFiles({ workspaceRoot, @@ -16,7 +15,6 @@ export default defineConfig({ resolve: { alias: { "@": path.resolve(juniorPackageRoot, "src"), - "@sentry/junior-memory": path.resolve(memoryPackageRoot, "src/index.ts"), "@sentry/junior-plugin-api": path.resolve( pluginApiPackageRoot, "src/index.ts", diff --git a/packages/junior-evals/vitest.evals.behavioral.config.ts b/packages/junior-evals/vitest.evals.behavioral.config.ts index d9ff75fab1..b116ca1c0a 100644 --- a/packages/junior-evals/vitest.evals.behavioral.config.ts +++ b/packages/junior-evals/vitest.evals.behavioral.config.ts @@ -8,7 +8,6 @@ const juniorPackageRoot = path.resolve(__dirname, "../junior"); const workspaceRoot = path.resolve(__dirname, "../.."); const evalsPackageRoot = __dirname; const pluginApiPackageRoot = path.resolve(__dirname, "../junior-plugin-api"); -const memoryPackageRoot = path.resolve(__dirname, "../junior-memory"); // Leave room for harness cleanup and rubric judging after a reply reaches its // separate 60-second behavior budget. const EVAL_TEST_TIMEOUT_MS = 120_000; @@ -47,7 +46,6 @@ export default defineConfig({ resolve: { alias: { "@": path.resolve(juniorPackageRoot, "src"), - "@sentry/junior-memory": path.resolve(memoryPackageRoot, "src/index.ts"), "@sentry/junior-plugin-api": path.resolve( pluginApiPackageRoot, "src/index.ts", diff --git a/packages/junior-evals/vitest.evals.guardian.config.ts b/packages/junior-evals/vitest.evals.guardian.config.ts index 2550a313e2..9eb5e903b9 100644 --- a/packages/junior-evals/vitest.evals.guardian.config.ts +++ b/packages/junior-evals/vitest.evals.guardian.config.ts @@ -8,7 +8,6 @@ const juniorPackageRoot = path.resolve(__dirname, "../junior"); const workspaceRoot = path.resolve(__dirname, "../.."); const evalsPackageRoot = __dirname; const pluginApiPackageRoot = path.resolve(__dirname, "../junior-plugin-api"); -const memoryPackageRoot = path.resolve(__dirname, "../junior-memory"); // Leave room for provider retry inside the separate 60-second review budget. const GUARDIAN_EVAL_TEST_TIMEOUT_MS = 90_000; const evalReportPath = path.resolve( @@ -35,7 +34,6 @@ export default defineConfig({ resolve: { alias: { "@": path.resolve(juniorPackageRoot, "src"), - "@sentry/junior-memory": path.resolve(memoryPackageRoot, "src/index.ts"), "@sentry/junior-plugin-api": path.resolve( pluginApiPackageRoot, "src/index.ts", diff --git a/packages/junior-evals/vitest.evals.integration.config.ts b/packages/junior-evals/vitest.evals.integration.config.ts index 904c987fba..dc18790d04 100644 --- a/packages/junior-evals/vitest.evals.integration.config.ts +++ b/packages/junior-evals/vitest.evals.integration.config.ts @@ -8,7 +8,6 @@ const juniorPackageRoot = path.resolve(__dirname, "../junior"); const workspaceRoot = path.resolve(__dirname, "../.."); const evalsPackageRoot = __dirname; const pluginApiPackageRoot = path.resolve(__dirname, "../junior-plugin-api"); -const memoryPackageRoot = path.resolve(__dirname, "../junior-memory"); // Leave room for harness cleanup and rubric judging after a reply reaches its // separate 60-second behavior budget. const EVAL_TEST_TIMEOUT_MS = 120_000; @@ -47,7 +46,6 @@ export default defineConfig({ resolve: { alias: { "@": path.resolve(juniorPackageRoot, "src"), - "@sentry/junior-memory": path.resolve(memoryPackageRoot, "src/index.ts"), "@sentry/junior-plugin-api": path.resolve( pluginApiPackageRoot, "src/index.ts", diff --git a/packages/junior-evals/vitest.evals.router.config.ts b/packages/junior-evals/vitest.evals.router.config.ts index 1c4f1d98d0..83d6244e24 100644 --- a/packages/junior-evals/vitest.evals.router.config.ts +++ b/packages/junior-evals/vitest.evals.router.config.ts @@ -8,7 +8,6 @@ const juniorPackageRoot = path.resolve(__dirname, "../junior"); const workspaceRoot = path.resolve(__dirname, "../.."); const evalsPackageRoot = __dirname; const pluginApiPackageRoot = path.resolve(__dirname, "../junior-plugin-api"); -const memoryPackageRoot = path.resolve(__dirname, "../junior-memory"); // Leave room for provider retry inside the separate 60-second route budget. const ROUTER_EVAL_TEST_TIMEOUT_MS = 90_000; const evalReportPath = path.resolve( @@ -35,7 +34,6 @@ export default defineConfig({ resolve: { alias: { "@": path.resolve(juniorPackageRoot, "src"), - "@sentry/junior-memory": path.resolve(memoryPackageRoot, "src/index.ts"), "@sentry/junior-plugin-api": path.resolve( pluginApiPackageRoot, "src/index.ts", diff --git a/packages/junior-memory/drizzle.config.ts b/packages/junior-memory/drizzle.config.ts deleted file mode 100644 index 15a62b834c..0000000000 --- a/packages/junior-memory/drizzle.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from "drizzle-kit"; - -export default defineConfig({ - dialect: "postgresql", - out: "./migrations", - schema: "./src/db/schema.ts", -}); diff --git a/packages/junior-memory/migrations/meta/0000_snapshot.json b/packages/junior-memory/migrations/meta/0000_snapshot.json deleted file mode 100644 index 75af75b1d8..0000000000 --- a/packages/junior-memory/migrations/meta/0000_snapshot.json +++ /dev/null @@ -1,234 +0,0 @@ -{ - "id": "57a69a4f-1cc5-4597-a818-fb23e3a5bf17", - "prevId": "00000000-0000-0000-0000-000000000000", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.junior_memory_memories": { - "name": "junior_memory_memories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scope_key": { - "name": "scope_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_type": { - "name": "subject_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_key": { - "name": "subject_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_platform": { - "name": "source_platform", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_key": { - "name": "source_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "observed_at_ms": { - "name": "observed_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "expires_at_ms": { - "name": "expires_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_at_ms": { - "name": "superseded_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_by_id": { - "name": "superseded_by_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived_at_ms": { - "name": "archived_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "archive_reason": { - "name": "archive_reason", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "junior_memory_memories_visible_idx": { - "name": "junior_memory_memories_visible_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at_ms", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_expiration_idx": { - "name": "junior_memory_memories_expiration_idx", - "columns": [ - { - "expression": "expires_at_ms", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_idempotency_idx": { - "name": "junior_memory_memories_idempotency_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "idempotency_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_memories_scope_check": { - "name": "junior_memory_memories_scope_check", - "value": "\"junior_memory_memories\".\"scope\" IN ('personal', 'conversation')" - }, - "junior_memory_memories_type_check": { - "name": "junior_memory_memories_type_check", - "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'identity',\n 'relationship',\n 'knowledge',\n 'context',\n 'event',\n 'task',\n 'observation'\n )" - }, - "junior_memory_memories_subject_type_check": { - "name": "junior_memory_memories_subject_type_check", - "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')" - }, - "junior_memory_memories_subject_key_check": { - "name": "junior_memory_memories_subject_key_check", - "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)" - }, - "junior_memory_memories_source_platform_check": { - "name": "junior_memory_memories_source_platform_check", - "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local')" - } - }, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/packages/junior-memory/migrations/meta/0001_snapshot.json b/packages/junior-memory/migrations/meta/0001_snapshot.json deleted file mode 100644 index f19e5deffa..0000000000 --- a/packages/junior-memory/migrations/meta/0001_snapshot.json +++ /dev/null @@ -1,234 +0,0 @@ -{ - "id": "aef16541-5bbe-4c1b-867d-a19c841cb278", - "prevId": "57a69a4f-1cc5-4597-a818-fb23e3a5bf17", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.junior_memory_memories": { - "name": "junior_memory_memories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scope_key": { - "name": "scope_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_type": { - "name": "subject_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_key": { - "name": "subject_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_platform": { - "name": "source_platform", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_key": { - "name": "source_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "observed_at_ms": { - "name": "observed_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "expires_at_ms": { - "name": "expires_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_at_ms": { - "name": "superseded_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_by_id": { - "name": "superseded_by_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived_at_ms": { - "name": "archived_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "archive_reason": { - "name": "archive_reason", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "junior_memory_memories_visible_idx": { - "name": "junior_memory_memories_visible_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at_ms", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_expiration_idx": { - "name": "junior_memory_memories_expiration_idx", - "columns": [ - { - "expression": "expires_at_ms", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_idempotency_idx": { - "name": "junior_memory_memories_idempotency_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "idempotency_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL AND \"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_memories_scope_check": { - "name": "junior_memory_memories_scope_check", - "value": "\"junior_memory_memories\".\"scope\" IN ('personal', 'conversation')" - }, - "junior_memory_memories_type_check": { - "name": "junior_memory_memories_type_check", - "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'identity',\n 'relationship',\n 'knowledge',\n 'context',\n 'event',\n 'task',\n 'observation'\n )" - }, - "junior_memory_memories_subject_type_check": { - "name": "junior_memory_memories_subject_type_check", - "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')" - }, - "junior_memory_memories_subject_key_check": { - "name": "junior_memory_memories_subject_key_check", - "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)" - }, - "junior_memory_memories_source_platform_check": { - "name": "junior_memory_memories_source_platform_check", - "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local')" - } - }, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/packages/junior-memory/migrations/meta/0002_snapshot.json b/packages/junior-memory/migrations/meta/0002_snapshot.json deleted file mode 100644 index 6f2bdbffc0..0000000000 --- a/packages/junior-memory/migrations/meta/0002_snapshot.json +++ /dev/null @@ -1,348 +0,0 @@ -{ - "id": "9e9985ce-7ac3-4872-97f3-e4a4d3fe39c9", - "prevId": "aef16541-5bbe-4c1b-867d-a19c841cb278", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.junior_memory_embeddings": { - "name": "junior_memory_embeddings", - "schema": "", - "columns": { - "memory_id": { - "name": "memory_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model": { - "name": "model", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "dimensions": { - "name": "dimensions", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "metric": { - "name": "metric", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "content_hash": { - "name": "content_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "embedding": { - "name": "embedding", - "type": "vector(1536)", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "junior_memory_embeddings_model_idx": { - "name": "junior_memory_embeddings_model_idx", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "model", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "dimensions", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "metric", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk": { - "name": "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk", - "tableFrom": "junior_memory_embeddings", - "tableTo": "junior_memory_memories", - "columnsFrom": ["memory_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_embeddings_metric_check": { - "name": "junior_memory_embeddings_metric_check", - "value": "\"junior_memory_embeddings\".\"metric\" IN ('cosine')" - }, - "junior_memory_embeddings_dimensions_check": { - "name": "junior_memory_embeddings_dimensions_check", - "value": "\"junior_memory_embeddings\".\"dimensions\" = 1536" - } - }, - "isRLSEnabled": false - }, - "public.junior_memory_memories": { - "name": "junior_memory_memories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scope_key": { - "name": "scope_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_type": { - "name": "subject_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_key": { - "name": "subject_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_platform": { - "name": "source_platform", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_key": { - "name": "source_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "observed_at_ms": { - "name": "observed_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "expires_at_ms": { - "name": "expires_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_at_ms": { - "name": "superseded_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_by_id": { - "name": "superseded_by_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived_at_ms": { - "name": "archived_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "archive_reason": { - "name": "archive_reason", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "junior_memory_memories_visible_idx": { - "name": "junior_memory_memories_visible_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at_ms", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_expiration_idx": { - "name": "junior_memory_memories_expiration_idx", - "columns": [ - { - "expression": "expires_at_ms", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_idempotency_idx": { - "name": "junior_memory_memories_idempotency_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "idempotency_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL AND \"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_memories_scope_check": { - "name": "junior_memory_memories_scope_check", - "value": "\"junior_memory_memories\".\"scope\" IN ('personal', 'conversation')" - }, - "junior_memory_memories_type_check": { - "name": "junior_memory_memories_type_check", - "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'identity',\n 'relationship',\n 'knowledge',\n 'context',\n 'event',\n 'task',\n 'observation'\n )" - }, - "junior_memory_memories_subject_type_check": { - "name": "junior_memory_memories_subject_type_check", - "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')" - }, - "junior_memory_memories_subject_key_check": { - "name": "junior_memory_memories_subject_key_check", - "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)" - }, - "junior_memory_memories_source_platform_check": { - "name": "junior_memory_memories_source_platform_check", - "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local')" - } - }, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/packages/junior-memory/migrations/meta/0003_snapshot.json b/packages/junior-memory/migrations/meta/0003_snapshot.json deleted file mode 100644 index 0845a395d1..0000000000 --- a/packages/junior-memory/migrations/meta/0003_snapshot.json +++ /dev/null @@ -1,348 +0,0 @@ -{ - "id": "2f78c37c-9c80-4f92-92f8-7eaa1951862b", - "prevId": "9e9985ce-7ac3-4872-97f3-e4a4d3fe39c9", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.junior_memory_embeddings": { - "name": "junior_memory_embeddings", - "schema": "", - "columns": { - "memory_id": { - "name": "memory_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model": { - "name": "model", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "dimensions": { - "name": "dimensions", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "metric": { - "name": "metric", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "content_hash": { - "name": "content_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "embedding": { - "name": "embedding", - "type": "vector(1536)", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "junior_memory_embeddings_model_idx": { - "name": "junior_memory_embeddings_model_idx", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "model", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "dimensions", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "metric", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "with": {}, - "method": "btree", - "concurrently": false - } - }, - "foreignKeys": { - "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk": { - "name": "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk", - "tableFrom": "junior_memory_embeddings", - "columnsFrom": ["memory_id"], - "tableTo": "junior_memory_memories", - "columnsTo": ["id"], - "onUpdate": "no action", - "onDelete": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_embeddings_metric_check": { - "name": "junior_memory_embeddings_metric_check", - "value": "\"junior_memory_embeddings\".\"metric\" IN ('cosine')" - }, - "junior_memory_embeddings_dimensions_check": { - "name": "junior_memory_embeddings_dimensions_check", - "value": "\"junior_memory_embeddings\".\"dimensions\" = 1536" - } - }, - "isRLSEnabled": false - }, - "public.junior_memory_memories": { - "name": "junior_memory_memories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scope_key": { - "name": "scope_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_type": { - "name": "subject_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_key": { - "name": "subject_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_platform": { - "name": "source_platform", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_key": { - "name": "source_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "observed_at_ms": { - "name": "observed_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "expires_at_ms": { - "name": "expires_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_at_ms": { - "name": "superseded_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_by_id": { - "name": "superseded_by_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived_at_ms": { - "name": "archived_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "archive_reason": { - "name": "archive_reason", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "junior_memory_memories_visible_idx": { - "name": "junior_memory_memories_visible_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at_ms", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "with": {}, - "method": "btree", - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false - }, - "junior_memory_memories_expiration_idx": { - "name": "junior_memory_memories_expiration_idx", - "columns": [ - { - "expression": "expires_at_ms", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "with": {}, - "method": "btree", - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL", - "concurrently": false - }, - "junior_memory_memories_idempotency_idx": { - "name": "junior_memory_memories_idempotency_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "idempotency_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "with": {}, - "method": "btree", - "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL AND \"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_memories_scope_check": { - "name": "junior_memory_memories_scope_check", - "value": "\"junior_memory_memories\".\"scope\" IN ('personal', 'conversation')" - }, - "junior_memory_memories_type_check": { - "name": "junior_memory_memories_type_check", - "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'identity',\n 'relationship',\n 'knowledge',\n 'context',\n 'event',\n 'task',\n 'observation'\n )" - }, - "junior_memory_memories_subject_type_check": { - "name": "junior_memory_memories_subject_type_check", - "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')" - }, - "junior_memory_memories_subject_key_check": { - "name": "junior_memory_memories_subject_key_check", - "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)" - }, - "junior_memory_memories_source_platform_check": { - "name": "junior_memory_memories_source_platform_check", - "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local')" - } - }, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "views": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/packages/junior-memory/migrations/meta/0004_snapshot.json b/packages/junior-memory/migrations/meta/0004_snapshot.json deleted file mode 100644 index 252285d546..0000000000 --- a/packages/junior-memory/migrations/meta/0004_snapshot.json +++ /dev/null @@ -1,348 +0,0 @@ -{ - "id": "e915b537-996a-488c-a828-7cf4da06be44", - "prevId": "2f78c37c-9c80-4f92-92f8-7eaa1951862b", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.junior_memory_embeddings": { - "name": "junior_memory_embeddings", - "schema": "", - "columns": { - "memory_id": { - "name": "memory_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model": { - "name": "model", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "dimensions": { - "name": "dimensions", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "metric": { - "name": "metric", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "content_hash": { - "name": "content_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "embedding": { - "name": "embedding", - "type": "vector(1536)", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "junior_memory_embeddings_model_idx": { - "name": "junior_memory_embeddings_model_idx", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "model", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "dimensions", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "metric", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk": { - "name": "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk", - "tableFrom": "junior_memory_embeddings", - "tableTo": "junior_memory_memories", - "columnsFrom": ["memory_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_embeddings_metric_check": { - "name": "junior_memory_embeddings_metric_check", - "value": "\"junior_memory_embeddings\".\"metric\" IN ('cosine')" - }, - "junior_memory_embeddings_dimensions_check": { - "name": "junior_memory_embeddings_dimensions_check", - "value": "\"junior_memory_embeddings\".\"dimensions\" = 1536" - } - }, - "isRLSEnabled": false - }, - "public.junior_memory_memories": { - "name": "junior_memory_memories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scope_key": { - "name": "scope_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_type": { - "name": "subject_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_key": { - "name": "subject_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_platform": { - "name": "source_platform", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_key": { - "name": "source_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "observed_at_ms": { - "name": "observed_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "expires_at_ms": { - "name": "expires_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_at_ms": { - "name": "superseded_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_by_id": { - "name": "superseded_by_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived_at_ms": { - "name": "archived_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "archive_reason": { - "name": "archive_reason", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "junior_memory_memories_visible_idx": { - "name": "junior_memory_memories_visible_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at_ms", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_expiration_idx": { - "name": "junior_memory_memories_expiration_idx", - "columns": [ - { - "expression": "expires_at_ms", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_idempotency_idx": { - "name": "junior_memory_memories_idempotency_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "idempotency_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL AND \"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_memories_scope_check": { - "name": "junior_memory_memories_scope_check", - "value": "\"junior_memory_memories\".\"scope\" IN ('personal', 'conversation')" - }, - "junior_memory_memories_kind_check": { - "name": "junior_memory_memories_kind_check", - "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'procedure',\n 'knowledge'\n )" - }, - "junior_memory_memories_subject_type_check": { - "name": "junior_memory_memories_subject_type_check", - "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')" - }, - "junior_memory_memories_subject_key_check": { - "name": "junior_memory_memories_subject_key_check", - "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)" - }, - "junior_memory_memories_source_platform_check": { - "name": "junior_memory_memories_source_platform_check", - "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local')" - } - }, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/packages/junior-memory/migrations/meta/0005_snapshot.json b/packages/junior-memory/migrations/meta/0005_snapshot.json deleted file mode 100644 index 7fb0fe5c5f..0000000000 --- a/packages/junior-memory/migrations/meta/0005_snapshot.json +++ /dev/null @@ -1,364 +0,0 @@ -{ - "id": "6948ea3f-2e44-49ce-aa94-8a993ce61cec", - "prevId": "e915b537-996a-488c-a828-7cf4da06be44", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.junior_memory_embeddings": { - "name": "junior_memory_embeddings", - "schema": "", - "columns": { - "memory_id": { - "name": "memory_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model": { - "name": "model", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "dimensions": { - "name": "dimensions", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "metric": { - "name": "metric", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "content_hash": { - "name": "content_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "embedding": { - "name": "embedding", - "type": "vector(1536)", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "junior_memory_embeddings_model_idx": { - "name": "junior_memory_embeddings_model_idx", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "model", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "dimensions", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "metric", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk": { - "name": "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk", - "tableFrom": "junior_memory_embeddings", - "tableTo": "junior_memory_memories", - "columnsFrom": ["memory_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_embeddings_metric_check": { - "name": "junior_memory_embeddings_metric_check", - "value": "\"junior_memory_embeddings\".\"metric\" IN ('cosine')" - }, - "junior_memory_embeddings_dimensions_check": { - "name": "junior_memory_embeddings_dimensions_check", - "value": "\"junior_memory_embeddings\".\"dimensions\" = 1536" - } - }, - "isRLSEnabled": false - }, - "public.junior_memory_memories": { - "name": "junior_memory_memories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scope_key": { - "name": "scope_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_type": { - "name": "subject_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_key": { - "name": "subject_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_platform": { - "name": "source_platform", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_key": { - "name": "source_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "observed_at_ms": { - "name": "observed_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "expires_at_ms": { - "name": "expires_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_at_ms": { - "name": "superseded_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_by_id": { - "name": "superseded_by_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived_at_ms": { - "name": "archived_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "archive_reason": { - "name": "archive_reason", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "junior_memory_memories_visible_idx": { - "name": "junior_memory_memories_visible_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at_ms", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_expiration_idx": { - "name": "junior_memory_memories_expiration_idx", - "columns": [ - { - "expression": "expires_at_ms", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_search_idx": { - "name": "junior_memory_memories_search_idx", - "columns": [ - { - "expression": "to_tsvector('english', \"content\")", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "gin", - "with": {} - }, - "junior_memory_memories_idempotency_idx": { - "name": "junior_memory_memories_idempotency_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "idempotency_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL AND \"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_memories_scope_check": { - "name": "junior_memory_memories_scope_check", - "value": "\"junior_memory_memories\".\"scope\" IN ('personal', 'conversation')" - }, - "junior_memory_memories_kind_check": { - "name": "junior_memory_memories_kind_check", - "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'procedure',\n 'knowledge'\n )" - }, - "junior_memory_memories_subject_type_check": { - "name": "junior_memory_memories_subject_type_check", - "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')" - }, - "junior_memory_memories_subject_key_check": { - "name": "junior_memory_memories_subject_key_check", - "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)" - }, - "junior_memory_memories_source_platform_check": { - "name": "junior_memory_memories_source_platform_check", - "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local')" - } - }, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/packages/junior-memory/migrations/meta/0006_snapshot.json b/packages/junior-memory/migrations/meta/0006_snapshot.json deleted file mode 100644 index c3916bff56..0000000000 --- a/packages/junior-memory/migrations/meta/0006_snapshot.json +++ /dev/null @@ -1,386 +0,0 @@ -{ - "id": "f18fa309-6485-40f3-921a-5d65552fbdf6", - "prevId": "6948ea3f-2e44-49ce-aa94-8a993ce61cec", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.junior_memory_embeddings": { - "name": "junior_memory_embeddings", - "schema": "", - "columns": { - "memory_id": { - "name": "memory_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model": { - "name": "model", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "dimensions": { - "name": "dimensions", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "metric": { - "name": "metric", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "content_hash": { - "name": "content_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "embedding": { - "name": "embedding", - "type": "vector(1536)", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "junior_memory_embeddings_model_idx": { - "name": "junior_memory_embeddings_model_idx", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "model", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "dimensions", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "metric", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk": { - "name": "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk", - "tableFrom": "junior_memory_embeddings", - "tableTo": "junior_memory_memories", - "columnsFrom": ["memory_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_embeddings_metric_check": { - "name": "junior_memory_embeddings_metric_check", - "value": "\"junior_memory_embeddings\".\"metric\" IN ('cosine')" - }, - "junior_memory_embeddings_dimensions_check": { - "name": "junior_memory_embeddings_dimensions_check", - "value": "\"junior_memory_embeddings\".\"dimensions\" = 1536" - } - }, - "isRLSEnabled": false - }, - "public.junior_memory_memories": { - "name": "junior_memory_memories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scope_key": { - "name": "scope_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_type": { - "name": "subject_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_key": { - "name": "subject_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "search_vector": { - "name": "search_vector", - "type": "tsvector", - "primaryKey": false, - "notNull": false, - "generated": { - "as": "to_tsvector('english', \"content\")", - "type": "stored" - } - }, - "source_platform": { - "name": "source_platform", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_key": { - "name": "source_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "observed_at_ms": { - "name": "observed_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "expires_at_ms": { - "name": "expires_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_at_ms": { - "name": "superseded_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_by_id": { - "name": "superseded_by_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived_at_ms": { - "name": "archived_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "archive_reason": { - "name": "archive_reason", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "junior_memory_memories_visible_idx": { - "name": "junior_memory_memories_visible_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at_ms", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_expiration_idx": { - "name": "junior_memory_memories_expiration_idx", - "columns": [ - { - "expression": "expires_at_ms", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_search_idx": { - "name": "junior_memory_memories_search_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "search_vector", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "gin", - "with": {} - }, - "junior_memory_memories_idempotency_idx": { - "name": "junior_memory_memories_idempotency_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "idempotency_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL AND \"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_memories_scope_check": { - "name": "junior_memory_memories_scope_check", - "value": "\"junior_memory_memories\".\"scope\" IN ('personal', 'conversation')" - }, - "junior_memory_memories_kind_check": { - "name": "junior_memory_memories_kind_check", - "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'procedure',\n 'knowledge'\n )" - }, - "junior_memory_memories_subject_type_check": { - "name": "junior_memory_memories_subject_type_check", - "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')" - }, - "junior_memory_memories_subject_key_check": { - "name": "junior_memory_memories_subject_key_check", - "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)" - }, - "junior_memory_memories_source_platform_check": { - "name": "junior_memory_memories_source_platform_check", - "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local')" - } - }, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/packages/junior-memory/migrations/meta/0007_snapshot.json b/packages/junior-memory/migrations/meta/0007_snapshot.json deleted file mode 100644 index c4de024de3..0000000000 --- a/packages/junior-memory/migrations/meta/0007_snapshot.json +++ /dev/null @@ -1,409 +0,0 @@ -{ - "id": "eeef198b-34d7-4ab8-9e1e-1a238ce8bdd5", - "prevId": "f18fa309-6485-40f3-921a-5d65552fbdf6", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.junior_memory_embeddings": { - "name": "junior_memory_embeddings", - "schema": "", - "columns": { - "memory_id": { - "name": "memory_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model": { - "name": "model", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "dimensions": { - "name": "dimensions", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "metric": { - "name": "metric", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "content_hash": { - "name": "content_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "embedding": { - "name": "embedding", - "type": "vector(1536)", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "junior_memory_embeddings_model_idx": { - "name": "junior_memory_embeddings_model_idx", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "model", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "dimensions", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "metric", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_embeddings_embedding_hnsw_idx": { - "name": "junior_memory_embeddings_embedding_hnsw_idx", - "columns": [ - { - "expression": "embedding", - "isExpression": false, - "asc": true, - "nulls": "last", - "opclass": "vector_cosine_ops" - } - ], - "isUnique": false, - "concurrently": false, - "method": "hnsw", - "with": { - "m": 16, - "ef_construction": 64 - } - } - }, - "foreignKeys": { - "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk": { - "name": "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk", - "tableFrom": "junior_memory_embeddings", - "tableTo": "junior_memory_memories", - "columnsFrom": [ - "memory_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_embeddings_metric_check": { - "name": "junior_memory_embeddings_metric_check", - "value": "\"junior_memory_embeddings\".\"metric\" IN ('cosine')" - }, - "junior_memory_embeddings_dimensions_check": { - "name": "junior_memory_embeddings_dimensions_check", - "value": "\"junior_memory_embeddings\".\"dimensions\" = 1536" - } - }, - "isRLSEnabled": false - }, - "public.junior_memory_memories": { - "name": "junior_memory_memories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scope_key": { - "name": "scope_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_type": { - "name": "subject_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_key": { - "name": "subject_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "search_vector": { - "name": "search_vector", - "type": "tsvector", - "primaryKey": false, - "notNull": false, - "generated": { - "as": "to_tsvector('english', \"content\")", - "type": "stored" - } - }, - "source_platform": { - "name": "source_platform", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_key": { - "name": "source_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "observed_at_ms": { - "name": "observed_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "expires_at_ms": { - "name": "expires_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_at_ms": { - "name": "superseded_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_by_id": { - "name": "superseded_by_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived_at_ms": { - "name": "archived_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "archive_reason": { - "name": "archive_reason", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "junior_memory_memories_visible_idx": { - "name": "junior_memory_memories_visible_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at_ms", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_expiration_idx": { - "name": "junior_memory_memories_expiration_idx", - "columns": [ - { - "expression": "expires_at_ms", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_search_idx": { - "name": "junior_memory_memories_search_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "search_vector", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "gin", - "with": {} - }, - "junior_memory_memories_idempotency_idx": { - "name": "junior_memory_memories_idempotency_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "idempotency_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL AND \"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_memories_scope_check": { - "name": "junior_memory_memories_scope_check", - "value": "\"junior_memory_memories\".\"scope\" IN ('personal', 'conversation')" - }, - "junior_memory_memories_kind_check": { - "name": "junior_memory_memories_kind_check", - "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'procedure',\n 'knowledge'\n )" - }, - "junior_memory_memories_subject_type_check": { - "name": "junior_memory_memories_subject_type_check", - "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')" - }, - "junior_memory_memories_subject_key_check": { - "name": "junior_memory_memories_subject_key_check", - "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)" - }, - "junior_memory_memories_source_platform_check": { - "name": "junior_memory_memories_source_platform_check", - "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local')" - } - }, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/packages/junior-memory/migrations/meta/0008_snapshot.json b/packages/junior-memory/migrations/meta/0008_snapshot.json deleted file mode 100644 index 917e60f987..0000000000 --- a/packages/junior-memory/migrations/meta/0008_snapshot.json +++ /dev/null @@ -1,409 +0,0 @@ -{ - "id": "030f5fc1-7062-4287-bd0b-14fcfd1c6a65", - "prevId": "eeef198b-34d7-4ab8-9e1e-1a238ce8bdd5", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.junior_memory_embeddings": { - "name": "junior_memory_embeddings", - "schema": "", - "columns": { - "memory_id": { - "name": "memory_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model": { - "name": "model", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "dimensions": { - "name": "dimensions", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "metric": { - "name": "metric", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "content_hash": { - "name": "content_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "embedding": { - "name": "embedding", - "type": "vector(1536)", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "junior_memory_embeddings_model_idx": { - "name": "junior_memory_embeddings_model_idx", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "model", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "dimensions", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "metric", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_embeddings_embedding_hnsw_idx": { - "name": "junior_memory_embeddings_embedding_hnsw_idx", - "columns": [ - { - "expression": "embedding", - "isExpression": false, - "asc": true, - "nulls": "last", - "opclass": "vector_cosine_ops" - } - ], - "isUnique": false, - "concurrently": false, - "method": "hnsw", - "with": { - "m": 16, - "ef_construction": 64 - } - } - }, - "foreignKeys": { - "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk": { - "name": "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk", - "tableFrom": "junior_memory_embeddings", - "tableTo": "junior_memory_memories", - "columnsFrom": [ - "memory_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_embeddings_metric_check": { - "name": "junior_memory_embeddings_metric_check", - "value": "\"junior_memory_embeddings\".\"metric\" IN ('cosine')" - }, - "junior_memory_embeddings_dimensions_check": { - "name": "junior_memory_embeddings_dimensions_check", - "value": "\"junior_memory_embeddings\".\"dimensions\" = 1536" - } - }, - "isRLSEnabled": false - }, - "public.junior_memory_memories": { - "name": "junior_memory_memories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scope_key": { - "name": "scope_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_type": { - "name": "subject_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_key": { - "name": "subject_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "search_vector": { - "name": "search_vector", - "type": "tsvector", - "primaryKey": false, - "notNull": false, - "generated": { - "as": "to_tsvector('english', \"content\")", - "type": "stored" - } - }, - "source_platform": { - "name": "source_platform", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_key": { - "name": "source_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "observed_at_ms": { - "name": "observed_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "expires_at_ms": { - "name": "expires_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_at_ms": { - "name": "superseded_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_by_id": { - "name": "superseded_by_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived_at_ms": { - "name": "archived_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "archive_reason": { - "name": "archive_reason", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "junior_memory_memories_visible_idx": { - "name": "junior_memory_memories_visible_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at_ms", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_expiration_idx": { - "name": "junior_memory_memories_expiration_idx", - "columns": [ - { - "expression": "expires_at_ms", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_search_idx": { - "name": "junior_memory_memories_search_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "search_vector", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "gin", - "with": {} - }, - "junior_memory_memories_idempotency_idx": { - "name": "junior_memory_memories_idempotency_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "idempotency_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL AND \"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_memories_scope_check": { - "name": "junior_memory_memories_scope_check", - "value": "\"junior_memory_memories\".\"scope\" IN ('personal', 'conversation')" - }, - "junior_memory_memories_kind_check": { - "name": "junior_memory_memories_kind_check", - "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'procedure',\n 'knowledge'\n )" - }, - "junior_memory_memories_subject_type_check": { - "name": "junior_memory_memories_subject_type_check", - "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')" - }, - "junior_memory_memories_subject_key_check": { - "name": "junior_memory_memories_subject_key_check", - "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)" - }, - "junior_memory_memories_source_platform_check": { - "name": "junior_memory_memories_source_platform_check", - "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local', 'web')" - } - }, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/packages/junior-memory/migrations/meta/0009_snapshot.json b/packages/junior-memory/migrations/meta/0009_snapshot.json deleted file mode 100644 index 50ddd075a3..0000000000 --- a/packages/junior-memory/migrations/meta/0009_snapshot.json +++ /dev/null @@ -1,411 +0,0 @@ -{ - "id": "bf82349c-95b3-4edf-b8a0-2bc597b7c8b4", - "prevId": "030f5fc1-7062-4287-bd0b-14fcfd1c6a65", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.junior_memory_embeddings": { - "name": "junior_memory_embeddings", - "schema": "", - "columns": { - "memory_id": { - "name": "memory_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model": { - "name": "model", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "dimensions": { - "name": "dimensions", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "metric": { - "name": "metric", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "content_hash": { - "name": "content_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "embedding": { - "name": "embedding", - "type": "vector(1536)", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "junior_memory_embeddings_model_idx": { - "name": "junior_memory_embeddings_model_idx", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "model", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "dimensions", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "metric", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_embeddings_embedding_hnsw_idx": { - "name": "junior_memory_embeddings_embedding_hnsw_idx", - "columns": [ - { - "expression": "embedding", - "isExpression": false, - "asc": true, - "nulls": "last", - "opclass": "vector_cosine_ops" - } - ], - "isUnique": false, - "concurrently": false, - "method": "hnsw", - "with": { - "m": 16, - "ef_construction": 64 - } - } - }, - "foreignKeys": { - "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk": { - "name": "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk", - "tableFrom": "junior_memory_embeddings", - "tableTo": "junior_memory_memories", - "columnsFrom": ["memory_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_embeddings_metric_check": { - "name": "junior_memory_embeddings_metric_check", - "value": "\"junior_memory_embeddings\".\"metric\" IN ('cosine')" - }, - "junior_memory_embeddings_dimensions_check": { - "name": "junior_memory_embeddings_dimensions_check", - "value": "\"junior_memory_embeddings\".\"dimensions\" = 1536" - } - }, - "isRLSEnabled": false - }, - "public.junior_memory_memories": { - "name": "junior_memory_memories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scope_key": { - "name": "scope_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_type": { - "name": "subject_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_key": { - "name": "subject_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "search_vector": { - "name": "search_vector", - "type": "tsvector", - "primaryKey": false, - "notNull": false, - "generated": { - "as": "to_tsvector('english', \"content\")", - "type": "stored" - } - }, - "source_platform": { - "name": "source_platform", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_key": { - "name": "source_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "observed_at_ms": { - "name": "observed_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "expires_at_ms": { - "name": "expires_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_at_ms": { - "name": "superseded_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_by_id": { - "name": "superseded_by_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived_at_ms": { - "name": "archived_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "archive_reason": { - "name": "archive_reason", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "junior_memory_memories_visible_idx": { - "name": "junior_memory_memories_visible_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at_ms", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_expiration_idx": { - "name": "junior_memory_memories_expiration_idx", - "columns": [ - { - "expression": "expires_at_ms", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_search_idx": { - "name": "junior_memory_memories_search_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "search_vector", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "gin", - "with": {} - }, - "junior_memory_memories_idempotency_idx": { - "name": "junior_memory_memories_idempotency_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "idempotency_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL AND \"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_memories_scope_check": { - "name": "junior_memory_memories_scope_check", - "value": "\"junior_memory_memories\".\"scope\" IN ('private', 'public')" - }, - "junior_memory_memories_kind_check": { - "name": "junior_memory_memories_kind_check", - "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'procedure',\n 'knowledge'\n )" - }, - "junior_memory_memories_subject_type_check": { - "name": "junior_memory_memories_subject_type_check", - "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')" - }, - "junior_memory_memories_subject_key_check": { - "name": "junior_memory_memories_subject_key_check", - "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)" - }, - "junior_memory_memories_source_platform_check": { - "name": "junior_memory_memories_source_platform_check", - "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local', 'web')" - } - }, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/packages/junior-memory/migrations/meta/0010_snapshot.json b/packages/junior-memory/migrations/meta/0010_snapshot.json deleted file mode 100644 index f84f41ff9a..0000000000 --- a/packages/junior-memory/migrations/meta/0010_snapshot.json +++ /dev/null @@ -1,415 +0,0 @@ -{ - "id": "3094a78d-54ba-42dc-926d-7563a6a651bd", - "prevId": "bf82349c-95b3-4edf-b8a0-2bc597b7c8b4", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.junior_memory_embeddings": { - "name": "junior_memory_embeddings", - "schema": "", - "columns": { - "memory_id": { - "name": "memory_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model": { - "name": "model", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "dimensions": { - "name": "dimensions", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "metric": { - "name": "metric", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "content_hash": { - "name": "content_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "embedding": { - "name": "embedding", - "type": "vector(1536)", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "junior_memory_embeddings_model_idx": { - "name": "junior_memory_embeddings_model_idx", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "model", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "dimensions", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "metric", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_embeddings_embedding_hnsw_idx": { - "name": "junior_memory_embeddings_embedding_hnsw_idx", - "columns": [ - { - "expression": "embedding", - "isExpression": false, - "asc": true, - "nulls": "last", - "opclass": "vector_cosine_ops" - } - ], - "isUnique": false, - "concurrently": false, - "method": "hnsw", - "with": { - "m": 16, - "ef_construction": 64 - } - } - }, - "foreignKeys": { - "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk": { - "name": "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk", - "tableFrom": "junior_memory_embeddings", - "tableTo": "junior_memory_memories", - "columnsFrom": [ - "memory_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_embeddings_metric_check": { - "name": "junior_memory_embeddings_metric_check", - "value": "\"junior_memory_embeddings\".\"metric\" IN ('cosine')" - }, - "junior_memory_embeddings_dimensions_check": { - "name": "junior_memory_embeddings_dimensions_check", - "value": "\"junior_memory_embeddings\".\"dimensions\" = 1536" - } - }, - "isRLSEnabled": false - }, - "public.junior_memory_memories": { - "name": "junior_memory_memories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scope_key": { - "name": "scope_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_type": { - "name": "subject_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_key": { - "name": "subject_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "search_vector": { - "name": "search_vector", - "type": "tsvector", - "primaryKey": false, - "notNull": false, - "generated": { - "as": "to_tsvector('english', \"content\")", - "type": "stored" - } - }, - "source_platform": { - "name": "source_platform", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_key": { - "name": "source_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "observed_at_ms": { - "name": "observed_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "expires_at_ms": { - "name": "expires_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_at_ms": { - "name": "superseded_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_by_id": { - "name": "superseded_by_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived_at_ms": { - "name": "archived_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "archive_reason": { - "name": "archive_reason", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "junior_memory_memories_visible_idx": { - "name": "junior_memory_memories_visible_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at_ms", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_expiration_idx": { - "name": "junior_memory_memories_expiration_idx", - "columns": [ - { - "expression": "expires_at_ms", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_search_idx": { - "name": "junior_memory_memories_search_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "search_vector", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "gin", - "with": {} - }, - "junior_memory_memories_idempotency_idx": { - "name": "junior_memory_memories_idempotency_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "idempotency_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL AND \"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_memories_scope_check": { - "name": "junior_memory_memories_scope_check", - "value": "\"junior_memory_memories\".\"scope\" IN ('private', 'public')" - }, - "junior_memory_memories_kind_check": { - "name": "junior_memory_memories_kind_check", - "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'procedure',\n 'knowledge'\n )" - }, - "junior_memory_memories_subject_type_check": { - "name": "junior_memory_memories_subject_type_check", - "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')" - }, - "junior_memory_memories_subject_key_check": { - "name": "junior_memory_memories_subject_key_check", - "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)" - }, - "junior_memory_memories_source_platform_check": { - "name": "junior_memory_memories_source_platform_check", - "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local', 'web')" - } - }, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/packages/junior-memory/migrations/meta/0011_snapshot.json b/packages/junior-memory/migrations/meta/0011_snapshot.json deleted file mode 100644 index 71dfc203e6..0000000000 --- a/packages/junior-memory/migrations/meta/0011_snapshot.json +++ /dev/null @@ -1,417 +0,0 @@ -{ - "id": "90b7d7a0-b8df-402b-afbd-dcf6dc898261", - "prevId": "3094a78d-54ba-42dc-926d-7563a6a651bd", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.junior_memory_embeddings": { - "name": "junior_memory_embeddings", - "schema": "", - "columns": { - "memory_id": { - "name": "memory_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model": { - "name": "model", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "dimensions": { - "name": "dimensions", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "metric": { - "name": "metric", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "content_hash": { - "name": "content_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "embedding": { - "name": "embedding", - "type": "vector(1536)", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "junior_memory_embeddings_model_idx": { - "name": "junior_memory_embeddings_model_idx", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "model", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "dimensions", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "metric", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_embeddings_embedding_hnsw_idx": { - "name": "junior_memory_embeddings_embedding_hnsw_idx", - "columns": [ - { - "expression": "embedding", - "isExpression": false, - "asc": true, - "nulls": "last", - "opclass": "vector_cosine_ops" - } - ], - "isUnique": false, - "concurrently": false, - "method": "hnsw", - "with": { - "m": 16, - "ef_construction": 64 - } - } - }, - "foreignKeys": { - "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk": { - "name": "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk", - "tableFrom": "junior_memory_embeddings", - "tableTo": "junior_memory_memories", - "columnsFrom": ["memory_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_embeddings_metric_check": { - "name": "junior_memory_embeddings_metric_check", - "value": "\"junior_memory_embeddings\".\"metric\" IN ('cosine')" - }, - "junior_memory_embeddings_dimensions_check": { - "name": "junior_memory_embeddings_dimensions_check", - "value": "\"junior_memory_embeddings\".\"dimensions\" = 1536" - } - }, - "isRLSEnabled": false - }, - "public.junior_memory_memories": { - "name": "junior_memory_memories", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scope_key": { - "name": "scope_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_type": { - "name": "subject_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "subject_key": { - "name": "subject_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "search_vector": { - "name": "search_vector", - "type": "tsvector", - "primaryKey": false, - "notNull": false, - "generated": { - "as": "to_tsvector('english', \"content\")", - "type": "stored" - } - }, - "source_platform": { - "name": "source_platform", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_key": { - "name": "source_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "conversation_id": { - "name": "conversation_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "observed_at_ms": { - "name": "observed_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "created_at_ms": { - "name": "created_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "expires_at_ms": { - "name": "expires_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_at_ms": { - "name": "superseded_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "superseded_by_id": { - "name": "superseded_by_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived_at_ms": { - "name": "archived_at_ms", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "archive_reason": { - "name": "archive_reason", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "junior_memory_memories_visible_idx": { - "name": "junior_memory_memories_visible_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at_ms", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_expiration_idx": { - "name": "junior_memory_memories_expiration_idx", - "columns": [ - { - "expression": "expires_at_ms", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "junior_memory_memories_search_idx": { - "name": "junior_memory_memories_search_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "search_vector", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "gin", - "with": {} - }, - "junior_memory_memories_idempotency_idx": { - "name": "junior_memory_memories_idempotency_idx", - "columns": [ - { - "expression": "scope", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "scope_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "idempotency_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL AND \"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "junior_memory_memories_scope_check": { - "name": "junior_memory_memories_scope_check", - "value": "\"junior_memory_memories\".\"scope\" IN ('private', 'public')" - }, - "junior_memory_memories_kind_check": { - "name": "junior_memory_memories_kind_check", - "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'procedure',\n 'knowledge'\n )" - }, - "junior_memory_memories_subject_type_check": { - "name": "junior_memory_memories_subject_type_check", - "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')" - }, - "junior_memory_memories_subject_key_check": { - "name": "junior_memory_memories_subject_key_check", - "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)" - }, - "junior_memory_memories_source_platform_check": { - "name": "junior_memory_memories_source_platform_check", - "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local', 'web')" - } - }, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/packages/junior-memory/package.json b/packages/junior-memory/package.json deleted file mode 100644 index 4d58cae339..0000000000 --- a/packages/junior-memory/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@sentry/junior-memory", - "version": "0.222.0", - "private": false, - "publishConfig": { - "access": "public" - }, - "type": "module", - "repository": { - "type": "git", - "url": "git+https://github.com/getsentry/junior.git", - "directory": "packages/junior-memory" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "files": [ - "dist", - "migrations" - ], - "scripts": { - "build": "tsup", - "db:generate": "pnpm exec drizzle-kit generate --config drizzle.config.ts", - "lint": "oxlint --config ../junior/.oxlintrc.json --deny-warnings src tests tsup.config.ts", - "prepare": "pnpm run build", - "prepack": "pnpm run build", - "test": "vitest run", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@sentry/junior-plugin-api": "workspace:*", - "@sinclair/typebox": "^0.34.49", - "commander": "^14.0.3", - "drizzle-orm": "catalog:", - "zod": "catalog:" - }, - "devDependencies": { - "@oxlint/plugins": "1.79.0", - "@sentry/junior-testing": "workspace:*", - "@types/node": "^25.9.1", - "drizzle-kit": "catalog:", - "oxlint": "^1.79.0", - "tsup": "^8.5.1", - "typescript": "^6.0.3", - "vitest": "^4.1.7" - } -} diff --git a/packages/junior-memory/src/index.ts b/packages/junior-memory/src/index.ts deleted file mode 100644 index 96ef0fca86..0000000000 --- a/packages/junior-memory/src/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -export { memoryPlugin } from "./plugin"; -export { - memoryApiSchema, - memoryDashboardResponseSchema, - memoryListResponseSchema, - type MemoryApi, - type MemoryDashboardResponse, - type MemoryListResponse, -} from "./api"; -export type { MemoryPluginOptions } from "./plugin"; -export { createMemoryStore } from "./store"; -export type { - ArchiveMemoryInput, - CreateMemoryInput, - CreateMemoryResult, - ListMemoriesInput, - MemoryDb, - MemoryEmbeddingProvider, - MemoryRecord, - MemoryStore, - MemoryStoreOptions, - SearchMemoriesInput, -} from "./store"; -export { MEMORY_KINDS } from "./types"; -export type { MemoryKind, MemoryRuntimeContext } from "./types"; diff --git a/packages/junior-memory/tsconfig.build.json b/packages/junior-memory/tsconfig.build.json deleted file mode 100644 index b398e67a86..0000000000 --- a/packages/junior-memory/tsconfig.build.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "declaration": true, - "incremental": false, - "noEmit": false, - "outDir": "dist", - "rootDir": "src" - } -} diff --git a/packages/junior-memory/tsconfig.json b/packages/junior-memory/tsconfig.json deleted file mode 100644 index 8c5ea28503..0000000000 --- a/packages/junior-memory/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "lib": ["ES2022"], - "module": "ESNext", - "moduleResolution": "Bundler", - "strict": true, - "skipLibCheck": true, - "isolatedModules": true, - "noEmit": true, - "types": ["node"] - }, - "include": ["src/**/*.ts"] -} diff --git a/packages/junior-memory/tsup.config.ts b/packages/junior-memory/tsup.config.ts deleted file mode 100644 index 91816ef4dd..0000000000 --- a/packages/junior-memory/tsup.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { defineConfig } from "tsup"; - -export default defineConfig({ - clean: true, - dts: { - compilerOptions: { - // TODO(upstream): Remove after tsup stops adding deprecated baseUrl. - ignoreDeprecations: "6.0", - }, - }, - entry: ["src/index.ts"], - format: ["esm"], - sourcemap: true, - target: "node24", -}); diff --git a/packages/junior-memory/vitest.config.ts b/packages/junior-memory/vitest.config.ts deleted file mode 100644 index 25bf25596b..0000000000 --- a/packages/junior-memory/vitest.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import path from "node:path"; -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - resolve: { - alias: { - "@sentry/junior-plugin-api": path.resolve( - __dirname, - "../junior-plugin-api/src/index.ts", - ), - }, - }, - test: { - environment: "node", - include: ["tests/**/*.test.ts"], - }, -}); diff --git a/packages/junior-plugin-api/src/user-pages.ts b/packages/junior-plugin-api/src/user-pages.ts index 3c46f531a3..40268efe46 100644 --- a/packages/junior-plugin-api/src/user-pages.ts +++ b/packages/junior-plugin-api/src/user-pages.ts @@ -36,7 +36,8 @@ const pluginUserPageActionSchema = z confirmation: nonBlankStringSchema.max(500).optional(), href: nonBlankStringSchema .max(500) - .regex(/^\/api\/plugins\/[a-z][a-z0-9-]*(?:\/|$)/), + // Plugins act under /api/plugins//; core features under /api//. + .regex(/^\/api\/(?:plugins\/)?[a-z][a-z0-9-]*(?:\/|$)/), label: nonBlankStringSchema.max(80), method: z.literal("DELETE"), tone: z.enum(["danger", "neutral"]).optional(), diff --git a/packages/junior-testing/src/pglite.ts b/packages/junior-testing/src/pglite.ts index ca69aeae7b..8b7109d770 100644 --- a/packages/junior-testing/src/pglite.ts +++ b/packages/junior-testing/src/pglite.ts @@ -5,12 +5,17 @@ import { type Transaction, } from "@electric-sql/pglite"; import { btree_gin as pgliteBtreeGinExtension } from "@electric-sql/pglite/contrib/btree_gin"; +import { pgcrypto as pglitePgcryptoExtension } from "@electric-sql/pglite/contrib/pgcrypto"; import { vector as pgliteVectorExtension } from "@electric-sql/pglite/vector"; import { drizzle } from "drizzle-orm/pglite"; type PgliteQueryClient = PGlite | Transaction; -export { pgliteBtreeGinExtension, pgliteVectorExtension }; +export { + pgliteBtreeGinExtension, + pglitePgcryptoExtension, + pgliteVectorExtension, +}; export interface LocalPgliteFixture { client: PGlite; diff --git a/packages/junior/migrations/0045_memory_core.sql b/packages/junior/migrations/0045_memory_core.sql new file mode 100644 index 0000000000..d34fe81c20 --- /dev/null +++ b/packages/junior/migrations/0045_memory_core.sql @@ -0,0 +1,408 @@ +-- Memory moved from the @sentry/junior-memory plugin into core. +-- +-- This migration adopts the Memory schema in one core journal entry. Every +-- step checks the current database state first, so a fresh database, a fully +-- migrated legacy database, and every partial legacy prefix reach the same +-- final schema without replaying data rewrites that already ran. +CREATE EXTENSION IF NOT EXISTS vector; +--> statement-breakpoint +CREATE EXTENSION IF NOT EXISTS btree_gin; +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "junior_memory_memories" ( + "id" text PRIMARY KEY NOT NULL, + "scope" text NOT NULL, + "scope_key" text NOT NULL, + "type" text NOT NULL, + "subject_type" text NOT NULL, + "subject_key" text, + "content" text NOT NULL, + "search_vector" "tsvector" GENERATED ALWAYS AS (to_tsvector('english', "content")) STORED, + "source_platform" text NOT NULL, + "source_key" text NOT NULL, + "location_id" text, + "conversation_id" text, + "idempotency_key" text, + "observed_at_ms" bigint NOT NULL, + "created_at_ms" bigint NOT NULL, + "expires_at_ms" bigint, + "superseded_at_ms" bigint, + "superseded_by_id" text, + "archived_at_ms" bigint, + "archive_reason" text, + CONSTRAINT "junior_memory_memories_scope_check" CHECK ("junior_memory_memories"."scope" IN ('private', 'public')), + CONSTRAINT "junior_memory_memories_kind_check" CHECK ("junior_memory_memories"."type" IN ( + 'preference', + 'procedure', + 'knowledge' + )), + CONSTRAINT "junior_memory_memories_subject_type_check" CHECK ("junior_memory_memories"."subject_type" IN ('user', 'conversation', 'general')), + CONSTRAINT "junior_memory_memories_subject_key_check" CHECK (("junior_memory_memories"."subject_type" = 'general' AND "junior_memory_memories"."subject_key" IS NULL) OR ("junior_memory_memories"."subject_type" IN ('user', 'conversation') AND "junior_memory_memories"."subject_key" IS NOT NULL AND length("junior_memory_memories"."subject_key") > 0)), + CONSTRAINT "junior_memory_memories_source_platform_check" CHECK ("junior_memory_memories"."source_platform" IN ('slack', 'local', 'web')) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "junior_memory_embeddings" ( + "memory_id" text PRIMARY KEY NOT NULL, + "provider" text NOT NULL, + "model" text NOT NULL, + "dimensions" integer NOT NULL, + "metric" text NOT NULL, + "content_hash" text NOT NULL, + "embedding" vector(1536) NOT NULL, + "created_at_ms" bigint NOT NULL, + CONSTRAINT "junior_memory_embeddings_metric_check" CHECK ("junior_memory_embeddings"."metric" IN ('cosine')), + CONSTRAINT "junior_memory_embeddings_dimensions_check" CHECK ("junior_memory_embeddings"."dimensions" = 1536), + CONSTRAINT "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk" FOREIGN KEY ("memory_id") REFERENCES "public"."junior_memory_memories"("id") ON DELETE cascade ON UPDATE no action +); +--> statement-breakpoint +-- Legacy plugin history 0001-0011: bring an existing Memory table to the +-- final shape in the original order. Each block runs only when its +-- transition is missing, and data rewrites run only when legacy rows remain. +DO $$ +DECLARE + idempotency_index text; + search_index text; + source_platform_check text; + scope_check text; +BEGIN + -- 0001: the idempotency index applies only to active rows. + SELECT indexdef INTO idempotency_index + FROM pg_indexes + WHERE schemaname = 'public' + AND indexname = 'junior_memory_memories_idempotency_idx'; + IF idempotency_index IS NOT NULL + AND idempotency_index NOT LIKE '%superseded_by_id IS NULL%' THEN + DROP INDEX "junior_memory_memories_idempotency_idx"; + END IF; + + -- 0003: legacy public Slack memory used channel-scoped keys. Convert them + -- to workspace keys and resolve active idempotency collisions first. + IF EXISTS ( + SELECT 1 FROM junior_memory_memories + WHERE scope = 'conversation' + AND source_platform = 'slack' + AND split_part(scope_key, ':', 1) = 'slack' + AND split_part(scope_key, ':', 3) LIKE 'C%' + AND split_part(scope_key, ':', 4) <> '' + ) THEN + WITH ranked_active_rows AS ( + SELECT + id, + is_legacy_public, + row_number() OVER ( + PARTITION BY target_scope_key, idempotency_key + ORDER BY CASE WHEN is_legacy_public THEN 1 ELSE 0 END, id + ) AS duplicate_rank + FROM ( + SELECT + id, + idempotency_key, + CASE + WHEN split_part(scope_key, ':', 1) = 'slack' + AND split_part(scope_key, ':', 3) LIKE 'C%' + AND split_part(scope_key, ':', 4) <> '' + THEN 'slack:' || split_part(scope_key, ':', 2) + ELSE scope_key + END AS target_scope_key, + split_part(scope_key, ':', 1) = 'slack' + AND split_part(scope_key, ':', 3) LIKE 'C%' + AND split_part(scope_key, ':', 4) <> '' AS is_legacy_public + FROM junior_memory_memories + WHERE scope = 'conversation' + AND source_platform = 'slack' + AND idempotency_key IS NOT NULL + AND archived_at_ms IS NULL + AND superseded_at_ms IS NULL + AND superseded_by_id IS NULL + ) active_rows + ) + UPDATE junior_memory_memories + SET idempotency_key = 'legacy-public-slack:' || junior_memory_memories.id + FROM ranked_active_rows + WHERE junior_memory_memories.id = ranked_active_rows.id + AND ranked_active_rows.is_legacy_public + AND ranked_active_rows.duplicate_rank > 1; + + UPDATE junior_memory_memories + SET + scope_key = 'slack:' || split_part(scope_key, ':', 2), + subject_key = CASE + WHEN subject_type = 'conversation' + AND subject_key IS NOT NULL + AND split_part(subject_key, ':', 1) = 'slack' + AND split_part(subject_key, ':', 3) LIKE 'C%' + AND split_part(subject_key, ':', 4) <> '' + THEN 'slack:' || split_part(subject_key, ':', 2) + ELSE subject_key + END + WHERE scope = 'conversation' + AND source_platform = 'slack' + AND split_part(scope_key, ':', 1) = 'slack' + AND split_part(scope_key, ':', 3) LIKE 'C%' + AND split_part(scope_key, ':', 4) <> ''; + END IF; + + -- 0004: legacy kinds collapse into preference, procedure, and knowledge. + IF EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'junior_memory_memories_type_check' + AND conrelid = 'public.junior_memory_memories'::regclass + ) THEN + ALTER TABLE "junior_memory_memories" DROP CONSTRAINT "junior_memory_memories_type_check"; + UPDATE "junior_memory_memories" + SET "type" = CASE + WHEN "type" = 'task' THEN 'procedure' + WHEN "type" IN ('identity', 'relationship', 'context', 'event', 'observation') THEN 'knowledge' + ELSE "type" + END; + END IF; + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'junior_memory_memories_kind_check' + AND conrelid = 'public.junior_memory_memories'::regclass + ) THEN + ALTER TABLE "junior_memory_memories" ADD CONSTRAINT "junior_memory_memories_kind_check" CHECK ("junior_memory_memories"."type" IN ( + 'preference', + 'procedure', + 'knowledge' + )); + END IF; + + -- 0005/0006: full-text search uses a stored generated column and a scoped + -- GIN index. Drop only the 0005 expression index. + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'junior_memory_memories' + AND column_name = 'search_vector' + ) THEN + ALTER TABLE "junior_memory_memories" ADD COLUMN "search_vector" "tsvector" GENERATED ALWAYS AS (to_tsvector('english', "content")) STORED; + END IF; + SELECT indexdef INTO search_index + FROM pg_indexes + WHERE schemaname = 'public' + AND indexname = 'junior_memory_memories_search_idx'; + IF search_index IS NOT NULL AND search_index NOT LIKE '%search_vector%' THEN + DROP INDEX "junior_memory_memories_search_idx"; + END IF; + + -- 0008: web is a memory source platform. + SELECT pg_get_constraintdef(oid) INTO source_platform_check + FROM pg_constraint + WHERE conname = 'junior_memory_memories_source_platform_check' + AND conrelid = 'public.junior_memory_memories'::regclass; + IF source_platform_check IS NOT NULL AND source_platform_check NOT LIKE '%web%' THEN + ALTER TABLE "junior_memory_memories" DROP CONSTRAINT "junior_memory_memories_source_platform_check"; + ALTER TABLE "junior_memory_memories" ADD CONSTRAINT "junior_memory_memories_source_platform_check" CHECK ("junior_memory_memories"."source_platform" IN ('slack', 'local', 'web')); + END IF; + + -- 0009: scope is private or public, rows record their Location, unowned + -- legacy rows are archived, and collisions keep distinct idempotency keys. + ALTER TABLE "junior_memory_memories" ADD COLUMN IF NOT EXISTS "location_id" text; + SELECT pg_get_constraintdef(oid) INTO scope_check + FROM pg_constraint + WHERE conname = 'junior_memory_memories_scope_check' + AND conrelid = 'public.junior_memory_memories'::regclass; + IF scope_check IS NOT NULL AND scope_check NOT LIKE '%private%' THEN + ALTER TABLE "junior_memory_memories" DROP CONSTRAINT "junior_memory_memories_scope_check"; + END IF; + IF EXISTS ( + SELECT 1 FROM junior_memory_memories + WHERE scope IN ('personal', 'conversation') + ) THEN + CREATE TEMP TABLE junior_memory_legacy_owners ( + id text PRIMARY KEY, + user_id text NOT NULL + ) ON COMMIT DROP; + IF to_regclass('junior_identities') IS NOT NULL THEN + EXECUTE $migration$ + INSERT INTO junior_memory_legacy_owners (id, user_id) + SELECT memory.id, owner.user_id + FROM junior_memory_memories AS memory + INNER JOIN LATERAL ( + SELECT identity.user_id + FROM junior_identities AS identity + WHERE identity.user_id IS NOT NULL + AND ( + ( + memory.scope_key LIKE 'slack:%' + AND identity.provider = 'slack' + AND identity.provider_tenant_id = split_part(memory.scope_key, ':', 2) + AND identity.provider_subject_id = split_part(memory.scope_key, ':', 3) + ) + OR ( + memory.scope_key LIKE 'local:%' + AND identity.provider = 'local' + AND identity.provider_subject_id = substring(memory.scope_key FROM 7) + ) + OR ( + memory.scope_key LIKE 'junior:%' + AND identity.provider = 'junior' + AND identity.provider_subject_id = substring(memory.scope_key FROM 8) + ) + ) + ORDER BY identity.id + LIMIT 1 + ) AS owner ON memory.scope = 'personal' + $migration$; + END IF; + CREATE TEMP TABLE junior_memory_scope_targets ON COMMIT DROP AS + SELECT + memory.id, + CASE + WHEN memory.scope = 'conversation' + AND memory.source_platform = 'slack' + AND memory.scope_key = 'slack:' || split_part(memory.source_key, ':', 2) + THEN 'public' + ELSE 'private' + END AS target_scope, + CASE + WHEN memory.scope = 'conversation' + AND memory.source_platform = 'slack' + AND memory.scope_key = 'slack:' || split_part(memory.source_key, ':', 2) + THEN 'public' + WHEN memory.scope = 'personal' AND owner.user_id IS NOT NULL + THEN owner.user_id + ELSE 'legacy-unowned:' || memory.id + END AS target_scope_key, + ( + memory.scope <> 'personal' AND NOT ( + memory.scope = 'conversation' + AND memory.source_platform = 'slack' + AND memory.scope_key = 'slack:' || split_part(memory.source_key, ':', 2) + ) + ) OR (memory.scope = 'personal' AND owner.user_id IS NULL) AS archive_unowned + FROM junior_memory_memories AS memory + LEFT JOIN junior_memory_legacy_owners AS owner ON owner.id = memory.id + WHERE memory.scope IN ('personal', 'conversation'); + WITH duplicate_targets AS ( + SELECT + memory.id, + row_number() OVER ( + PARTITION BY target.target_scope, target.target_scope_key, memory.idempotency_key + ORDER BY memory.id + ) AS duplicate_rank + FROM junior_memory_memories AS memory + INNER JOIN junior_memory_scope_targets AS target ON target.id = memory.id + WHERE memory.idempotency_key IS NOT NULL + AND memory.archived_at_ms IS NULL + AND memory.superseded_at_ms IS NULL + AND memory.superseded_by_id IS NULL + AND NOT target.archive_unowned + ) + UPDATE junior_memory_memories AS memory + SET idempotency_key = 'legacy-scope:' || memory.id + FROM duplicate_targets + WHERE memory.id = duplicate_targets.id + AND duplicate_targets.duplicate_rank > 1; + UPDATE junior_memory_memories AS memory + SET + scope = target.target_scope, + scope_key = target.target_scope_key, + subject_key = CASE + WHEN memory.subject_type = 'user' AND target.target_scope = 'private' + THEN target.target_scope_key + ELSE memory.subject_key + END, + archived_at_ms = CASE + WHEN target.archive_unowned + THEN coalesce(memory.archived_at_ms, floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint) + ELSE memory.archived_at_ms + END, + archive_reason = CASE + WHEN target.archive_unowned + THEN coalesce(memory.archive_reason, 'legacy_unowned_scope') + ELSE memory.archive_reason + END + FROM junior_memory_scope_targets AS target + WHERE memory.id = target.id; + DROP TABLE junior_memory_scope_targets; + DROP TABLE junior_memory_legacy_owners; + END IF; + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'junior_memory_memories_scope_check' + AND conrelid = 'public.junior_memory_memories'::regclass + ) THEN + ALTER TABLE "junior_memory_memories" ADD CONSTRAINT "junior_memory_memories_scope_check" CHECK ("junior_memory_memories"."scope" IN ('private', 'public')); + END IF; + + -- 0010: stored memories_captured v2 events use private and public labels. + IF to_regclass('public.junior_conversation_events') IS NOT NULL AND EXISTS ( + SELECT 1 + FROM junior_conversation_events AS event + WHERE event.type = 'structured_event' + AND event.payload->>'namespace' = 'memory' + AND event.payload->>'name' = 'memories_captured' + AND (event.payload->>'version') = '2' + AND jsonb_typeof(event.payload->'content'->'memories') = 'array' + AND EXISTS ( + SELECT 1 + FROM jsonb_array_elements(event.payload->'content'->'memories') AS memory(value) + WHERE memory.value->>'scope' IN ('personal', 'conversation') + ) + ) THEN + UPDATE junior_conversation_events AS event + SET payload = jsonb_set( + event.payload, + '{content,memories}', + ( + SELECT coalesce( + jsonb_agg( + CASE + WHEN jsonb_typeof(memory.value) = 'object' + AND memory.value->>'scope' = 'personal' + THEN jsonb_set(memory.value, '{scope}', '"private"'::jsonb) + WHEN jsonb_typeof(memory.value) = 'object' + AND memory.value->>'scope' = 'conversation' + THEN jsonb_set(memory.value, '{scope}', '"public"'::jsonb) + ELSE memory.value + END + ORDER BY memory.ordinality + ), + '[]'::jsonb + ) + FROM jsonb_array_elements(event.payload->'content'->'memories') WITH ORDINALITY AS memory(value, ordinality) + ) + ) + WHERE event.type = 'structured_event' + AND event.payload->>'namespace' = 'memory' + AND event.payload->>'name' = 'memories_captured' + AND (event.payload->>'version') = '2' + AND jsonb_typeof(event.payload->'content'->'memories') = 'array' + AND EXISTS ( + SELECT 1 + FROM jsonb_array_elements(event.payload->'content'->'memories') AS memory(value) + WHERE memory.value->>'scope' IN ('personal', 'conversation') + ); + END IF; + + -- 0011: rows record the Conversation where Junior learned them. Backfill + -- only when the column is new. Current code may store rows without one. + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'junior_memory_memories' + AND column_name = 'conversation_id' + ) THEN + ALTER TABLE "junior_memory_memories" ADD COLUMN "conversation_id" text; + UPDATE "junior_memory_memories" + SET "conversation_id" = "source_key" + WHERE "source_platform" IN ('web', 'local'); + UPDATE "junior_memory_memories" + SET "conversation_id" = 'slack:' || split_part("source_key", ':', 3) || ':' || split_part("source_key", ':', 4) + WHERE "source_platform" = 'slack'; + END IF; +END +$$; +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "junior_memory_memories_idempotency_idx" ON "junior_memory_memories" USING btree ("scope","scope_key","idempotency_key") WHERE "junior_memory_memories"."idempotency_key" IS NOT NULL AND "junior_memory_memories"."archived_at_ms" IS NULL AND "junior_memory_memories"."superseded_at_ms" IS NULL AND "junior_memory_memories"."superseded_by_id" IS NULL; +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "junior_memory_memories_visible_idx" ON "junior_memory_memories" USING btree ("scope","scope_key","created_at_ms" DESC NULLS LAST,"id") WHERE "junior_memory_memories"."archived_at_ms" IS NULL AND "junior_memory_memories"."superseded_at_ms" IS NULL AND "junior_memory_memories"."superseded_by_id" IS NULL; +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "junior_memory_memories_expiration_idx" ON "junior_memory_memories" USING btree ("expires_at_ms") WHERE "junior_memory_memories"."archived_at_ms" IS NULL AND "junior_memory_memories"."expires_at_ms" IS NOT NULL; +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "junior_memory_memories_search_idx" ON "junior_memory_memories" USING gin ("scope","scope_key","search_vector") WHERE "junior_memory_memories"."archived_at_ms" IS NULL AND "junior_memory_memories"."superseded_at_ms" IS NULL AND "junior_memory_memories"."superseded_by_id" IS NULL; +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "junior_memory_embeddings_model_idx" ON "junior_memory_embeddings" USING btree ("provider","model","dimensions","metric"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "junior_memory_embeddings_embedding_hnsw_idx" ON "junior_memory_embeddings" USING hnsw ("embedding" vector_cosine_ops) WITH (m=16,ef_construction=64); diff --git a/packages/junior/migrations/README.md b/packages/junior/migrations/README.md index 39d948d822..40a7015163 100644 --- a/packages/junior/migrations/README.md +++ b/packages/junior/migrations/README.md @@ -8,6 +8,10 @@ migrations. Core SQL is recorded in `drizzle.__drizzle_junior_core`. Reruns check that journal before taking the migration lock, so an already-current schema returns without opening a second SQL connection. +Core migrations need the `vector` (pgvector) and `btree_gin` Postgres +extensions. `junior upgrade` enables them under the migration lock. It stops +with a prerequisite error when the database cannot enable either one. + An existing Junior schema without the core Drizzle journal cannot be upgraded directly. Upgrade it with `@sentry/junior@0.107.1` first so that bridge release can establish the journal, then continue to the target version. A database with @@ -25,6 +29,22 @@ no Junior tables remains a normal fresh install. - Never edit, rename, reorder, or delete an applied SQL migration or its metadata. Add a new migration to correct it. -Migration loading, locking, and the bridge-version guard live in -`src/chat/conversations/sql/migrations.ts`. Their integration coverage lives in -`tests/integration/conversation-sql.test.ts`. +## Memory adoption + +`0045_memory_core` adopts the Memory tables that the removed +`@sentry/junior-memory` plugin used to own. It creates the final schema on a fresh database and +reconciles an existing database from any legacy plugin journal position, +applying only the transitions and data rewrites that are still missing. The +legacy plugin journal stays in the database as audit state. +`scripts/fixtures/legacy-memory-migrations/` keeps the plugin's 12 migrations +so that the rehearsal can build every legacy state. Rehearse the upgrade +against real Postgres with: + +```bash +DATABASE_URL=postgres://junior:junior@localhost:54322/junior \ + pnpm --filter @sentry/junior exec tsx scripts/rehearse-memory-upgrade.ts +``` + +Migration loading, locking, the extension check, and the bridge-version guard +live in `src/chat/conversations/sql/migrations.ts`. Their integration coverage +lives in `tests/integration/conversation-sql.test.ts`. diff --git a/packages/junior/migrations/meta/0045_snapshot.json b/packages/junior/migrations/meta/0045_snapshot.json new file mode 100644 index 0000000000..62d3371999 --- /dev/null +++ b/packages/junior/migrations/meta/0045_snapshot.json @@ -0,0 +1,4012 @@ +{ + "id": "6e392a4a-53d6-40d7-864e-51744d074e7b", + "prevId": "b83d9902-a77d-4fbc-8c38-d38d3d472201", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.junior_agent_bindings": { + "name": "junior_agent_bindings", + "schema": "", + "columns": { + "parent_conversation_id": { + "name": "parent_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_conversation_id": { + "name": "child_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "junior_agent_bindings_child_idx": { + "name": "junior_agent_bindings_child_idx", + "columns": [ + { + "expression": "child_conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "junior_agent_bindings_parent_conversation_id_junior_conversations_conversation_id_fk": { + "name": "junior_agent_bindings_parent_conversation_id_junior_conversations_conversation_id_fk", + "tableFrom": "junior_agent_bindings", + "tableTo": "junior_conversations", + "columnsFrom": [ + "parent_conversation_id" + ], + "columnsTo": [ + "conversation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "junior_agent_bindings_child_conversation_id_junior_conversations_conversation_id_fk": { + "name": "junior_agent_bindings_child_conversation_id_junior_conversations_conversation_id_fk", + "tableFrom": "junior_agent_bindings", + "tableTo": "junior_conversations", + "columnsFrom": [ + "child_conversation_id" + ], + "columnsTo": [ + "conversation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "junior_agent_bindings_parent_conversation_id_name_pk": { + "name": "junior_agent_bindings_parent_conversation_id_name_pk", + "columns": [ + "parent_conversation_id", + "name" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_agent_invocations": { + "name": "junior_agent_invocations", + "schema": "", + "columns": { + "invocation_id": { + "name": "invocation_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "parent_conversation_id": { + "name": "parent_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_conversation_id": { + "name": "child_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input": { + "name": "input", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_json": { + "name": "actor_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "credential_context_json": { + "name": "credential_context_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_json": { + "name": "source_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "destination_json": { + "name": "destination_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "destination_visibility": { + "name": "destination_visibility", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reasoning_level": { + "name": "reasoning_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mailbox_status": { + "name": "mailbox_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "junior_agent_invocations_child_idx": { + "name": "junior_agent_invocations_child_idx", + "columns": [ + { + "expression": "child_conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_agent_invocations_mailbox_idx": { + "name": "junior_agent_invocations_mailbox_idx", + "columns": [ + { + "expression": "mailbox_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "junior_agent_invocations_parent_conversation_id_junior_conversations_conversation_id_fk": { + "name": "junior_agent_invocations_parent_conversation_id_junior_conversations_conversation_id_fk", + "tableFrom": "junior_agent_invocations", + "tableTo": "junior_conversations", + "columnsFrom": [ + "parent_conversation_id" + ], + "columnsTo": [ + "conversation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "junior_agent_invocations_child_conversation_id_junior_conversations_conversation_id_fk": { + "name": "junior_agent_invocations_child_conversation_id_junior_conversations_conversation_id_fk", + "tableFrom": "junior_agent_invocations", + "tableTo": "junior_conversations", + "columnsFrom": [ + "child_conversation_id" + ], + "columnsTo": [ + "conversation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_api_tokens": { + "name": "junior_api_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_email_normalized": { + "name": "owner_email_normalized", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_suffix": { + "name": "token_suffix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "junior_api_tokens_token_hash_uidx": { + "name": "junior_api_tokens_token_hash_uidx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_api_tokens_owner_email_idx": { + "name": "junior_api_tokens_owner_email_idx", + "columns": [ + { + "expression": "owner_email_normalized", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_artifacts": { + "name": "junior_artifacts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ext": { + "name": "ext", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "delete_requested_at": { + "name": "delete_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "junior_artifacts_conversation_sha_uidx": { + "name": "junior_artifacts_conversation_sha_uidx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_artifacts_gc_idx": { + "name": "junior_artifacts_gc_idx", + "columns": [ + { + "expression": "delete_requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_artifacts_conversation_idx": { + "name": "junior_artifacts_conversation_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_attachments": { + "name": "junior_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_provider": { + "name": "storage_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vision_summary": { + "name": "vision_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "delete_requested_at": { + "name": "delete_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "junior_attachments_conversation_idx": { + "name": "junior_attachments_conversation_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_attachments_provider_idx": { + "name": "junior_attachments_provider_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_attachments_gc_idx": { + "name": "junior_attachments_gc_idx", + "columns": [ + { + "expression": "storage_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delete_requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "junior_attachments_conversation_id_junior_conversations_conversation_id_fk": { + "name": "junior_attachments_conversation_id_junior_conversations_conversation_id_fk", + "tableFrom": "junior_attachments", + "tableTo": "junior_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "conversation_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_task_executions": { + "name": "junior_task_executions", + "schema": "", + "columns": { + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "executed_at_ms": { + "name": "executed_at_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "junior_task_executions_task_time_idx": { + "name": "junior_task_executions_task_time_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "namespace", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "executed_at_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_task_executions_time_kind_idx": { + "name": "junior_task_executions_time_kind_idx", + "columns": [ + { + "expression": "executed_at_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_task_executions_conversation_time_idx": { + "name": "junior_task_executions_conversation_time_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "executed_at_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "junior_task_executions_conversation_id_junior_conversations_conversation_id_fk": { + "name": "junior_task_executions_conversation_id_junior_conversations_conversation_id_fk", + "tableFrom": "junior_task_executions", + "tableTo": "junior_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "conversation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "junior_task_executions_kind_namespace_execution_id_pk": { + "name": "junior_task_executions_kind_namespace_execution_id_pk", + "columns": [ + "kind", + "namespace", + "execution_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "junior_task_executions_kind_check": { + "name": "junior_task_executions_kind_check", + "value": "\"junior_task_executions\".\"kind\" in ('scheduled', 'event')" + }, + "junior_task_executions_status_check": { + "name": "junior_task_executions_status_check", + "value": "\"junior_task_executions\".\"status\" in ('blocked', 'completed', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.junior_code_changes": { + "name": "junior_code_changes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "conversation_ids": { + "name": "conversation_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "merged_at": { + "name": "merged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "junior_code_changes_provider_id_idx": { + "name": "junior_code_changes_provider_id_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_code_changes_opened_at_idx": { + "name": "junior_code_changes_opened_at_idx", + "columns": [ + { + "expression": "opened_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_code_changes_merged_at_idx": { + "name": "junior_code_changes_merged_at_idx", + "columns": [ + { + "expression": "merged_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_code_changes_closed_at_idx": { + "name": "junior_code_changes_closed_at_idx", + "columns": [ + { + "expression": "closed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_code_changes_open_idx": { + "name": "junior_code_changes_open_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"junior_code_changes\".\"state\" = 'open'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "junior_code_changes_repository_id_junior_code_repositories_id_fk": { + "name": "junior_code_changes_repository_id_junior_code_repositories_id_fk", + "tableFrom": "junior_code_changes", + "tableTo": "junior_code_repositories", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_code_repositories": { + "name": "junior_code_repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "junior_code_repositories_provider_id_idx": { + "name": "junior_code_repositories_provider_id_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_conversation_annotations": { + "name": "junior_conversation_annotations", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin": { + "name": "plugin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "annotation_json": { + "name": "annotation_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "junior_conversation_annotations_conversation_id_fk": { + "name": "junior_conversation_annotations_conversation_id_fk", + "tableFrom": "junior_conversation_annotations", + "tableTo": "junior_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "conversation_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "junior_conversation_annotations_pk": { + "name": "junior_conversation_annotations_pk", + "columns": [ + "conversation_id", + "plugin", + "kind", + "key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_conversation_bindings": { + "name": "junior_conversation_bindings", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_tenant_id": { + "name": "provider_tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "provider_destination_id": { + "name": "provider_destination_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_conversation_id": { + "name": "provider_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "junior_conversation_bindings_conversation_idx": { + "name": "junior_conversation_bindings_conversation_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "junior_conversation_bindings_conversation_id_junior_conversations_conversation_id_fk": { + "name": "junior_conversation_bindings_conversation_id_junior_conversations_conversation_id_fk", + "tableFrom": "junior_conversation_bindings", + "tableTo": "junior_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "conversation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "junior_conversation_bindings_provider_conversation_pk": { + "name": "junior_conversation_bindings_provider_conversation_pk", + "columns": [ + "provider", + "provider_tenant_id", + "provider_destination_id", + "provider_conversation_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_conversation_briefs": { + "name": "junior_conversation_briefs", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "through_seq": { + "name": "through_seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "search_text": { + "name": "search_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cost_usd": { + "name": "cost_usd", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "junior_conversation_briefs_conversation_turn_idx": { + "name": "junior_conversation_briefs_conversation_turn_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "turn_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_conversation_briefs_conversation_version_idx": { + "name": "junior_conversation_briefs_conversation_version_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_conversation_briefs_search_idx": { + "name": "junior_conversation_briefs_search_idx", + "columns": [ + { + "expression": "to_tsvector('english', \"search_text\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "junior_conversation_briefs_conversation_id_junior_conversations_conversation_id_fk": { + "name": "junior_conversation_briefs_conversation_id_junior_conversations_conversation_id_fk", + "tableFrom": "junior_conversation_briefs", + "tableTo": "junior_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "conversation_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "junior_conversation_briefs_conversation_id_version_pk": { + "name": "junior_conversation_briefs_conversation_id_version_pk", + "columns": [ + "conversation_id", + "version" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_conversation_events": { + "name": "junior_conversation_events", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "history_version": { + "name": "history_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "actor_identity_id": { + "name": "actor_identity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "junior_conversation_events_history_version_idx": { + "name": "junior_conversation_events_history_version_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "history_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_conversation_events_type_idx": { + "name": "junior_conversation_events_type_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_conversation_events_actor_identity_idx": { + "name": "junior_conversation_events_actor_identity_idx", + "columns": [ + { + "expression": "actor_identity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_conversation_events_message_search_idx": { + "name": "junior_conversation_events_message_search_idx", + "columns": [ + { + "expression": "to_tsvector('english', \"payload\"->>'text')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"junior_conversation_events\".\"type\" = 'message'", + "concurrently": false, + "method": "gin", + "with": {} + }, + "junior_conversation_events_idempotency_idx": { + "name": "junior_conversation_events_idempotency_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "junior_conversation_events_actor_identity_id_junior_identities_id_fk": { + "name": "junior_conversation_events_actor_identity_id_junior_identities_id_fk", + "tableFrom": "junior_conversation_events", + "tableTo": "junior_identities", + "columnsFrom": [ + "actor_identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "junior_conversation_events_conversation_id_junior_conversations_conversation_id_fk": { + "name": "junior_conversation_events_conversation_id_junior_conversations_conversation_id_fk", + "tableFrom": "junior_conversation_events", + "tableTo": "junior_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "conversation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "junior_conversation_events_conversation_id_seq_pk": { + "name": "junior_conversation_events_conversation_id_seq_pk", + "columns": [ + "conversation_id", + "seq" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_conversation_metrics": { + "name": "junior_conversation_metrics", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "junior_conversation_metrics_occurred_at_metric_idx": { + "name": "junior_conversation_metrics_occurred_at_metric_idx", + "columns": [ + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "junior_conversation_metrics_conversation_id_junior_conversations_conversation_id_fk": { + "name": "junior_conversation_metrics_conversation_id_junior_conversations_conversation_id_fk", + "tableFrom": "junior_conversation_metrics", + "tableTo": "junior_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "conversation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "junior_conversation_metrics_conversation_id_run_id_metric_pk": { + "name": "junior_conversation_metrics_conversation_id_run_id_metric_pk", + "columns": [ + "conversation_id", + "run_id", + "metric" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_conversation_participants": { + "name": "junior_conversation_participants", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "root_conversation_id": { + "name": "root_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_message_at": { + "name": "last_message_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "junior_conversation_participants_user_activity_idx": { + "name": "junior_conversation_participants_user_activity_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_message_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_conversation_participants_root_idx": { + "name": "junior_conversation_participants_root_idx", + "columns": [ + { + "expression": "root_conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "junior_conversation_participants_user_id_junior_users_id_fk": { + "name": "junior_conversation_participants_user_id_junior_users_id_fk", + "tableFrom": "junior_conversation_participants", + "tableTo": "junior_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "junior_conversation_participants_root_conversation_id_junior_conversations_conversation_id_fk": { + "name": "junior_conversation_participants_root_conversation_id_junior_conversations_conversation_id_fk", + "tableFrom": "junior_conversation_participants", + "tableTo": "junior_conversations", + "columnsFrom": [ + "root_conversation_id" + ], + "columnsTo": [ + "conversation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "junior_conversation_participants_user_root_pk": { + "name": "junior_conversation_participants_user_root_pk", + "columns": [ + "user_id", + "root_conversation_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_conversations": { + "name": "junior_conversations", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_json": { + "name": "source_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "origin_type": { + "name": "origin_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "location_json": { + "name": "location_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "destination_id": { + "name": "destination_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "destination_json": { + "name": "destination_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "actor_identity_id": { + "name": "actor_identity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creator_identity_id": { + "name": "creator_identity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_identity_id": { + "name": "credential_subject_identity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_json": { + "name": "actor_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "channel_name": { + "name": "channel_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_activity_at": { + "name": "last_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "execution_updated_at": { + "name": "execution_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_status": { + "name": "execution_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_checkpoint_at": { + "name": "last_checkpoint_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "parent_conversation_id": { + "name": "parent_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "root_conversation_id": { + "name": "root_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transcript_purged_at": { + "name": "transcript_purged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_duration_ms": { + "name": "execution_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "execution_usage_json": { + "name": "execution_usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metric_run_id": { + "name": "metric_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "junior_conversations_last_activity_idx": { + "name": "junior_conversations_last_activity_idx", + "columns": [ + { + "expression": "last_activity_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_conversations_active_idx": { + "name": "junior_conversations_active_idx", + "columns": [ + { + "expression": "coalesce(\"execution_updated_at\", \"updated_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"junior_conversations\".\"execution_status\" <> 'idle'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_conversations_destination_activity_idx": { + "name": "junior_conversations_destination_activity_idx", + "columns": [ + { + "expression": "destination_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_activity_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_conversations_actor_activity_idx": { + "name": "junior_conversations_actor_activity_idx", + "columns": [ + { + "expression": "actor_identity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_activity_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_conversations_origin_idx": { + "name": "junior_conversations_origin_idx", + "columns": [ + { + "expression": "origin_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_activity_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_conversations_parent_idx": { + "name": "junior_conversations_parent_idx", + "columns": [ + { + "expression": "parent_conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_conversations_root_idx": { + "name": "junior_conversations_root_idx", + "columns": [ + { + "expression": "root_conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "junior_conversations_destination_id_junior_destinations_id_fk": { + "name": "junior_conversations_destination_id_junior_destinations_id_fk", + "tableFrom": "junior_conversations", + "tableTo": "junior_destinations", + "columnsFrom": [ + "destination_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "junior_conversations_actor_identity_id_junior_identities_id_fk": { + "name": "junior_conversations_actor_identity_id_junior_identities_id_fk", + "tableFrom": "junior_conversations", + "tableTo": "junior_identities", + "columnsFrom": [ + "actor_identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "junior_conversations_creator_identity_id_junior_identities_id_fk": { + "name": "junior_conversations_creator_identity_id_junior_identities_id_fk", + "tableFrom": "junior_conversations", + "tableTo": "junior_identities", + "columnsFrom": [ + "creator_identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "junior_conversations_credential_subject_identity_id_junior_identities_id_fk": { + "name": "junior_conversations_credential_subject_identity_id_junior_identities_id_fk", + "tableFrom": "junior_conversations", + "tableTo": "junior_identities", + "columnsFrom": [ + "credential_subject_identity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "junior_conversations_parent_conversation_id_junior_conversations_conversation_id_fk": { + "name": "junior_conversations_parent_conversation_id_junior_conversations_conversation_id_fk", + "tableFrom": "junior_conversations", + "tableTo": "junior_conversations", + "columnsFrom": [ + "parent_conversation_id" + ], + "columnsTo": [ + "conversation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "junior_conversations_root_conversation_id_junior_conversations_conversation_id_fk": { + "name": "junior_conversations_root_conversation_id_junior_conversations_conversation_id_fk", + "tableFrom": "junior_conversations", + "tableTo": "junior_conversations", + "columnsFrom": [ + "root_conversation_id" + ], + "columnsTo": [ + "conversation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_destinations": { + "name": "junior_destinations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_tenant_id": { + "name": "provider_tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "provider_destination_id": { + "name": "provider_destination_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_destination_id": { + "name": "parent_destination_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "junior_destinations_provider_destination_uidx": { + "name": "junior_destinations_provider_destination_uidx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_destination_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_destinations_provider_kind_idx": { + "name": "junior_destinations_provider_kind_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_event_tasks": { + "name": "junior_event_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at_ms": { + "name": "created_at_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "task_json": { + "name": "task_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "junior_event_tasks_team_idx": { + "name": "junior_event_tasks_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"junior_event_tasks\".\"status\" <> 'deleted'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_event_tasks_match_idx": { + "name": "junior_event_tasks_match_idx", + "columns": [ + { + "expression": "namespace", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"junior_event_tasks\".\"status\" <> 'deleted'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_identities": { + "name": "junior_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_tenant_id": { + "name": "provider_tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "provider_subject_id": { + "name": "provider_subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_normalized": { + "name": "email_normalized", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "junior_identities_provider_subject_uidx": { + "name": "junior_identities_provider_subject_uidx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_identities_user_idx": { + "name": "junior_identities_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_identities_verified_email_idx": { + "name": "junior_identities_verified_email_idx", + "columns": [ + { + "expression": "email_normalized", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"junior_identities\".\"email_verified\" = true AND \"junior_identities\".\"email_normalized\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_identities_kind_provider_idx": { + "name": "junior_identities_kind_provider_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "junior_identities_user_id_junior_users_id_fk": { + "name": "junior_identities_user_id_junior_users_id_fk", + "tableFrom": "junior_identities", + "tableTo": "junior_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_location_configurations": { + "name": "junior_location_configurations", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "junior_location_configurations_location_id_junior_destinations_id_fk": { + "name": "junior_location_configurations_location_id_junior_destinations_id_fk", + "tableFrom": "junior_location_configurations", + "tableTo": "junior_destinations", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "junior_location_configurations_location_id_key_pk": { + "name": "junior_location_configurations_location_id_key_pk", + "columns": [ + "location_id", + "key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_memory_embeddings": { + "name": "junior_memory_embeddings", + "schema": "", + "columns": { + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dimensions": { + "name": "dimensions", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "created_at_ms": { + "name": "created_at_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "junior_memory_embeddings_model_idx": { + "name": "junior_memory_embeddings_model_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dimensions", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_memory_embeddings_embedding_hnsw_idx": { + "name": "junior_memory_embeddings_embedding_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + } + }, + "foreignKeys": { + "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk": { + "name": "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk", + "tableFrom": "junior_memory_embeddings", + "tableTo": "junior_memory_memories", + "columnsFrom": [ + "memory_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "junior_memory_embeddings_metric_check": { + "name": "junior_memory_embeddings_metric_check", + "value": "\"junior_memory_embeddings\".\"metric\" IN ('cosine')" + }, + "junior_memory_embeddings_dimensions_check": { + "name": "junior_memory_embeddings_dimensions_check", + "value": "\"junior_memory_embeddings\".\"dimensions\" = 1536" + } + }, + "isRLSEnabled": false + }, + "public.junior_memory_memories": { + "name": "junior_memory_memories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_key": { + "name": "subject_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"content\")", + "type": "stored" + } + }, + "source_platform": { + "name": "source_platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_key": { + "name": "source_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "observed_at_ms": { + "name": "observed_at_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at_ms": { + "name": "created_at_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "expires_at_ms": { + "name": "expires_at_ms", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "superseded_at_ms": { + "name": "superseded_at_ms", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "superseded_by_id": { + "name": "superseded_by_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at_ms": { + "name": "archived_at_ms", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "archive_reason": { + "name": "archive_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "junior_memory_memories_visible_idx": { + "name": "junior_memory_memories_visible_idx", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at_ms", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_memory_memories_expiration_idx": { + "name": "junior_memory_memories_expiration_idx", + "columns": [ + { + "expression": "expires_at_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_memory_memories_search_idx": { + "name": "junior_memory_memories_search_idx", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "junior_memory_memories_idempotency_idx": { + "name": "junior_memory_memories_idempotency_idx", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL AND \"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "junior_memory_memories_scope_check": { + "name": "junior_memory_memories_scope_check", + "value": "\"junior_memory_memories\".\"scope\" IN ('private', 'public')" + }, + "junior_memory_memories_kind_check": { + "name": "junior_memory_memories_kind_check", + "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'procedure',\n 'knowledge'\n )" + }, + "junior_memory_memories_subject_type_check": { + "name": "junior_memory_memories_subject_type_check", + "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')" + }, + "junior_memory_memories_subject_key_check": { + "name": "junior_memory_memories_subject_key_check", + "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)" + }, + "junior_memory_memories_source_platform_check": { + "name": "junior_memory_memories_source_platform_check", + "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local', 'web')" + } + }, + "isRLSEnabled": false + }, + "public.junior_scheduler_runs": { + "name": "junior_scheduler_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scheduled_for_ms": { + "name": "scheduled_for_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "record": { + "name": "record", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "junior_scheduler_runs_task_status_idx": { + "name": "junior_scheduler_runs_task_status_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduled_for_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_scheduler_runs_status_idx": { + "name": "junior_scheduler_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduled_for_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_scheduler_tasks": { + "name": "junior_scheduler_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creator_slack_user_id": { + "name": "creator_slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creator_identity_id": { + "name": "creator_identity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "next_run_at_ms": { + "name": "next_run_at_ms", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "run_now_at_ms": { + "name": "run_now_at_ms", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at_ms": { + "name": "created_at_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "record": { + "name": "record", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "junior_scheduler_tasks_creator_idx": { + "name": "junior_scheduler_tasks_creator_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "creator_slack_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"junior_scheduler_tasks\".\"status\" <> 'deleted'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_scheduler_tasks_creator_identity_idx": { + "name": "junior_scheduler_tasks_creator_identity_idx", + "columns": [ + { + "expression": "creator_identity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"junior_scheduler_tasks\".\"status\" <> 'deleted' AND \"junior_scheduler_tasks\".\"creator_identity_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_scheduler_tasks_team_status_idx": { + "name": "junior_scheduler_tasks_team_status_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"junior_scheduler_tasks\".\"status\" <> 'deleted'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_scheduler_tasks_run_now_due_idx": { + "name": "junior_scheduler_tasks_run_now_due_idx", + "columns": [ + { + "expression": "run_now_at_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"junior_scheduler_tasks\".\"status\" = 'active' AND \"junior_scheduler_tasks\".\"run_now_at_ms\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_scheduler_tasks_next_run_due_idx": { + "name": "junior_scheduler_tasks_next_run_due_idx", + "columns": [ + { + "expression": "next_run_at_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"junior_scheduler_tasks\".\"status\" = 'active' AND \"junior_scheduler_tasks\".\"next_run_at_ms\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_snapshots": { + "name": "junior_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile_hash": { + "name": "profile_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "snapshot_id": { + "name": "snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "build_duration_ms": { + "name": "build_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "generated_at": { + "name": "generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "build_started_at": { + "name": "build_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "build_phase": { + "name": "build_phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "build_sandbox_name": { + "name": "build_sandbox_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "build_command_id": { + "name": "build_command_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "build_error": { + "name": "build_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "junior_snapshots_snapshot_id_uidx": { + "name": "junior_snapshots_snapshot_id_uidx", + "columns": [ + { + "expression": "snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_snapshots_workspace_idx": { + "name": "junior_snapshots_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_snapshots_workspace_profile_status_idx": { + "name": "junior_snapshots_workspace_profile_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_snapshots_active_build_uidx": { + "name": "junior_snapshots_active_build_uidx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"junior_snapshots\".\"status\" = 'building'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "junior_snapshots_workspace_status_idx": { + "name": "junior_snapshots_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "junior_snapshots_workspace_id_junior_workspaces_id_fk": { + "name": "junior_snapshots_workspace_id_junior_workspaces_id_fk", + "tableFrom": "junior_snapshots", + "tableTo": "junior_workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "junior_snapshots_status_check": { + "name": "junior_snapshots_status_check", + "value": "\"junior_snapshots\".\"status\" in ('building', 'failed', 'ready')" + }, + "junior_snapshots_build_phase_check": { + "name": "junior_snapshots_build_phase_check", + "value": "\"junior_snapshots\".\"build_phase\" is null or \"junior_snapshots\".\"build_phase\" in ('created', 'dependencies_installed', 'repositories_prepared')" + }, + "junior_snapshots_ready_fields_check": { + "name": "junior_snapshots_ready_fields_check", + "value": "\"junior_snapshots\".\"status\" <> 'ready' or (\"junior_snapshots\".\"snapshot_id\" is not null and \"junior_snapshots\".\"build_duration_ms\" is not null and \"junior_snapshots\".\"generated_at\" is not null)" + }, + "junior_snapshots_build_fields_check": { + "name": "junior_snapshots_build_fields_check", + "value": "\"junior_snapshots\".\"status\" = 'ready' or (\"junior_snapshots\".\"build_started_at\" is not null and \"junior_snapshots\".\"build_phase\" is not null)" + }, + "junior_snapshots_build_duration_check": { + "name": "junior_snapshots_build_duration_check", + "value": "\"junior_snapshots\".\"build_duration_ms\" is null or \"junior_snapshots\".\"build_duration_ms\" >= 0" + }, + "junior_snapshots_size_bytes_check": { + "name": "junior_snapshots_size_bytes_check", + "value": "\"junior_snapshots\".\"size_bytes\" is null or \"junior_snapshots\".\"size_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.junior_stats": { + "name": "junior_stats", + "schema": "", + "columns": { + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "junior_stats_date_namespace_metric_name_pk": { + "name": "junior_stats_date_namespace_metric_name_pk", + "columns": [ + "date", + "namespace", + "metric", + "name" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_users": { + "name": "junior_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "primary_email": { + "name": "primary_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "primary_email_normalized": { + "name": "primary_email_normalized", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "junior_users_primary_email_normalized_uidx": { + "name": "junior_users_primary_email_normalized_uidx", + "columns": [ + { + "expression": "primary_email_normalized", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_workspace_repos": { + "name": "junior_workspace_repos", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo": { + "name": "repo", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "junior_workspace_repos_workspace_id_junior_workspaces_id_fk": { + "name": "junior_workspace_repos_workspace_id_junior_workspaces_id_fk", + "tableFrom": "junior_workspace_repos", + "tableTo": "junior_workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "junior_workspace_repos_workspace_id_provider_repo_pk": { + "name": "junior_workspace_repos_workspace_id_provider_repo_pk", + "columns": [ + "workspace_id", + "provider", + "repo" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.junior_workspaces": { + "name": "junior_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "setup_script": { + "name": "setup_script", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "junior_workspaces_name_idx": { + "name": "junior_workspaces_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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/junior/migrations/meta/_journal.json b/packages/junior/migrations/meta/_journal.json index b35873b6a3..633fe72b80 100644 --- a/packages/junior/migrations/meta/_journal.json +++ b/packages/junior/migrations/meta/_journal.json @@ -316,6 +316,13 @@ "when": 1790115429065, "tag": "0044_event_automation_channel_destinations", "breakpoints": true + }, + { + "idx": 45, + "version": "7", + "when": 1790186530995, + "tag": "0045_memory_core", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/packages/junior/package.json b/packages/junior/package.json index 37717a65a4..2a046c1954 100644 --- a/packages/junior/package.json +++ b/packages/junior/package.json @@ -36,6 +36,10 @@ "types": "./dist/api/schema.d.ts", "default": "./dist/api/schema.js" }, + "./memory": { + "types": "./dist/memory.d.ts", + "default": "./dist/memory.js" + }, "./vercel": { "types": "./dist/vercel.d.ts", "default": "./dist/vercel.js" @@ -109,7 +113,6 @@ "@hono/node-server": "1.19.14", "@oxlint/plugins": "1.79.0", "@sentry/junior-github": "workspace:*", - "@sentry/junior-memory": "workspace:*", "@sentry/junior-testing": "workspace:*", "@types/node": "^25.9.1", "@types/pg": "^8.15.6", diff --git a/packages/junior-memory/migrations/0000_dizzy_millenium_guard.sql b/packages/junior/scripts/fixtures/legacy-memory-migrations/0000_dizzy_millenium_guard.sql similarity index 100% rename from packages/junior-memory/migrations/0000_dizzy_millenium_guard.sql rename to packages/junior/scripts/fixtures/legacy-memory-migrations/0000_dizzy_millenium_guard.sql diff --git a/packages/junior-memory/migrations/0001_closed_madrox.sql b/packages/junior/scripts/fixtures/legacy-memory-migrations/0001_closed_madrox.sql similarity index 100% rename from packages/junior-memory/migrations/0001_closed_madrox.sql rename to packages/junior/scripts/fixtures/legacy-memory-migrations/0001_closed_madrox.sql diff --git a/packages/junior-memory/migrations/0002_light_silver_centurion.sql b/packages/junior/scripts/fixtures/legacy-memory-migrations/0002_light_silver_centurion.sql similarity index 100% rename from packages/junior-memory/migrations/0002_light_silver_centurion.sql rename to packages/junior/scripts/fixtures/legacy-memory-migrations/0002_light_silver_centurion.sql diff --git a/packages/junior-memory/migrations/0003_public_slack_workspace_memory.sql b/packages/junior/scripts/fixtures/legacy-memory-migrations/0003_public_slack_workspace_memory.sql similarity index 100% rename from packages/junior-memory/migrations/0003_public_slack_workspace_memory.sql rename to packages/junior/scripts/fixtures/legacy-memory-migrations/0003_public_slack_workspace_memory.sql diff --git a/packages/junior-memory/migrations/0004_gigantic_celestials.sql b/packages/junior/scripts/fixtures/legacy-memory-migrations/0004_gigantic_celestials.sql similarity index 100% rename from packages/junior-memory/migrations/0004_gigantic_celestials.sql rename to packages/junior/scripts/fixtures/legacy-memory-migrations/0004_gigantic_celestials.sql diff --git a/packages/junior-memory/migrations/0005_short_jamie_braddock.sql b/packages/junior/scripts/fixtures/legacy-memory-migrations/0005_short_jamie_braddock.sql similarity index 100% rename from packages/junior-memory/migrations/0005_short_jamie_braddock.sql rename to packages/junior/scripts/fixtures/legacy-memory-migrations/0005_short_jamie_braddock.sql diff --git a/packages/junior-memory/migrations/0006_friendly_hydra.sql b/packages/junior/scripts/fixtures/legacy-memory-migrations/0006_friendly_hydra.sql similarity index 100% rename from packages/junior-memory/migrations/0006_friendly_hydra.sql rename to packages/junior/scripts/fixtures/legacy-memory-migrations/0006_friendly_hydra.sql diff --git a/packages/junior-memory/migrations/0007_military_tyger_tiger.sql b/packages/junior/scripts/fixtures/legacy-memory-migrations/0007_military_tyger_tiger.sql similarity index 100% rename from packages/junior-memory/migrations/0007_military_tyger_tiger.sql rename to packages/junior/scripts/fixtures/legacy-memory-migrations/0007_military_tyger_tiger.sql diff --git a/packages/junior-memory/migrations/0008_web_memory_source_platform.sql b/packages/junior/scripts/fixtures/legacy-memory-migrations/0008_web_memory_source_platform.sql similarity index 100% rename from packages/junior-memory/migrations/0008_web_memory_source_platform.sql rename to packages/junior/scripts/fixtures/legacy-memory-migrations/0008_web_memory_source_platform.sql diff --git a/packages/junior-memory/migrations/0009_faithful_whizzer.sql b/packages/junior/scripts/fixtures/legacy-memory-migrations/0009_faithful_whizzer.sql similarity index 100% rename from packages/junior-memory/migrations/0009_faithful_whizzer.sql rename to packages/junior/scripts/fixtures/legacy-memory-migrations/0009_faithful_whizzer.sql diff --git a/packages/junior-memory/migrations/0010_memories_captured_scope_labels.sql b/packages/junior/scripts/fixtures/legacy-memory-migrations/0010_memories_captured_scope_labels.sql similarity index 100% rename from packages/junior-memory/migrations/0010_memories_captured_scope_labels.sql rename to packages/junior/scripts/fixtures/legacy-memory-migrations/0010_memories_captured_scope_labels.sql diff --git a/packages/junior-memory/migrations/0011_memory_conversation_id.sql b/packages/junior/scripts/fixtures/legacy-memory-migrations/0011_memory_conversation_id.sql similarity index 100% rename from packages/junior-memory/migrations/0011_memory_conversation_id.sql rename to packages/junior/scripts/fixtures/legacy-memory-migrations/0011_memory_conversation_id.sql diff --git a/packages/junior-memory/migrations/meta/_journal.json b/packages/junior/scripts/fixtures/legacy-memory-migrations/meta/_journal.json similarity index 100% rename from packages/junior-memory/migrations/meta/_journal.json rename to packages/junior/scripts/fixtures/legacy-memory-migrations/meta/_journal.json diff --git a/packages/junior/scripts/rehearse-memory-upgrade.ts b/packages/junior/scripts/rehearse-memory-upgrade.ts new file mode 100644 index 0000000000..8a1218a217 --- /dev/null +++ b/packages/junior/scripts/rehearse-memory-upgrade.ts @@ -0,0 +1,398 @@ +/** + * Rehearse the core Memory migration against real Postgres. + * + * For a fresh database and for every meaningful legacy Memory plugin prefix, + * this script applies the pre-Memory core journal, the legacy plugin + * migrations, representative legacy rows, and then `junior upgrade`. It checks + * that every path ends with the same schema and data, and that a rerun makes + * no changes. + * + * Usage: + * DATABASE_URL=postgres://junior:junior@localhost:54322/junior \ + * pnpm --filter @sentry/junior exec tsx scripts/rehearse-memory-upgrade.ts + */ +import { + cpSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import pg from "pg"; +import { migrateSchema } from "@/chat/conversations/sql/migrations"; +import { migratePluginSchemas } from "@/chat/plugins/migrations"; +import type { JuniorSqlExecutor } from "@/db/db"; +import { createPostgresJuniorSqlExecutor } from "@/db/postgres"; + +const MEMORY_MIGRATION_TAG = "0045_memory_core"; +const CORE_MIGRATIONS = path.resolve(process.cwd(), "migrations"); +/** The 12 migrations that the removed `@sentry/junior-memory` plugin shipped. */ +const LEGACY_MIGRATIONS = path.resolve( + process.cwd(), + "scripts/fixtures/legacy-memory-migrations", +); +/** Legacy plugin journal prefixes worth rehearsing: before each data change. */ +const LEGACY_PREFIXES = [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12]; +const APPLICATION_NAME = "junior-memory-rehearsal"; + +interface Journal { + entries: Array<{ idx: number; tag: string; when: number }>; +} + +interface Fingerprint { + columns: unknown[]; + constraints: unknown[]; + extensions: unknown[]; + indexes: unknown[]; +} + +function trimmedMigrationsDir( + sourceDir: string, + keep: (entry: Journal["entries"][number]) => boolean, +): string { + const dir = mkdtempSync(path.join(tmpdir(), "junior-memory-rehearsal-")); + cpSync(sourceDir, dir, { recursive: true }); + const journalPath = path.join(dir, "meta", "_journal.json"); + const journal = JSON.parse(readFileSync(journalPath, "utf8")) as Journal; + journal.entries = journal.entries.filter(keep); + writeFileSync(journalPath, JSON.stringify(journal, null, 2)); + return dir; +} + +async function fingerprint(sql: JuniorSqlExecutor): Promise { + const tables = ["junior_memory_memories", "junior_memory_embeddings"]; + const columns = await sql.query( + `SELECT table_name, column_name, data_type, udt_name, is_nullable, is_generated, generation_expression + FROM information_schema.columns + WHERE table_schema = 'public' AND table_name = ANY($1) + ORDER BY table_name, column_name`, + [tables], + ); + const constraints = await sql.query( + `SELECT conrelid::regclass::text AS table_name, conname, pg_get_constraintdef(oid) AS definition + FROM pg_constraint + WHERE conrelid = ANY($1::regclass[]) + ORDER BY conrelid::regclass::text, conname`, + [tables], + ); + const indexes = await sql.query( + `SELECT tablename, indexname, indexdef + FROM pg_indexes + WHERE schemaname = 'public' AND tablename = ANY($1) + ORDER BY tablename, indexname`, + [tables], + ); + const extensions = await sql.query( + `SELECT extname FROM pg_extension WHERE extname IN ('vector', 'btree_gin') ORDER BY extname`, + ); + return { columns, constraints, extensions, indexes }; +} + +function legacyRows(prefix: number): Array> { + // Values mirror what the legacy plugin wrote at that journal position. + const scoped = prefix <= 9; + const userScopeKey = scoped ? "slack:T1:U1" : "user-1"; + return [ + { + id: "memory-a", + scope: scoped ? "personal" : "private", + scope_key: userScopeKey, + type: "preference", + subject_type: "user", + subject_key: userScopeKey, + source_platform: "slack", + source_key: "slack:T1:C1:1.1", + idempotency_key: "k-a", + }, + { + id: "memory-b", + scope: scoped ? "conversation" : "public", + scope_key: + prefix <= 3 ? "slack:T1:C1:1.1" : scoped ? "slack:T1" : "public", + type: "knowledge", + subject_type: "conversation", + subject_key: + prefix <= 3 ? "slack:T1:C1:1.1" : scoped ? "slack:T1" : "public", + source_platform: "slack", + source_key: "slack:T1:C1:1.1", + idempotency_key: "k-b", + }, + { + id: "memory-c", + scope: scoped ? "personal" : "private", + scope_key: userScopeKey, + type: prefix <= 4 ? "task" : "procedure", + subject_type: "general", + subject_key: null, + source_platform: "local", + source_key: "local:conversation-1", + idempotency_key: "k-c", + }, + ]; +} + +async function seedLegacyData( + sql: JuniorSqlExecutor, + prefix: number, +): Promise { + const now = new Date().toISOString(); + await sql.execute( + `INSERT INTO junior_users (id, primary_email, primary_email_normalized, created_at, updated_at) + VALUES ('user-1', 'one@example.com', 'one@example.com', $1, $1)`, + [now], + ); + await sql.execute( + `INSERT INTO junior_identities (id, kind, provider, provider_tenant_id, provider_subject_id, created_at, updated_at, user_id) + VALUES ('identity-1', 'user', 'slack', 'T1', 'U1', $1, $1, 'user-1')`, + [now], + ); + await sql.execute( + `INSERT INTO junior_conversations (conversation_id, created_at, last_activity_at, updated_at, execution_status) + VALUES ('slack:C1:1.1', $1, $1, $1, 'idle')`, + [now], + ); + await sql.execute( + `INSERT INTO junior_conversation_events (conversation_id, seq, history_version, type, payload, created_at) + VALUES ('slack:C1:1.1', 1, 1, 'structured_event', $1::jsonb, $2)`, + [ + JSON.stringify({ + namespace: "memory", + name: "memories_captured", + version: 2, + turnId: "turn-1", + content: { + memories: [ + { + id: "memory-a", + content: "Prefers terse updates.", + kind: "preference", + observedAtMs: 1, + scope: prefix <= 10 ? "personal" : "private", + }, + ], + }, + }), + now, + ], + ); + if (prefix === 0) { + return; + } + for (const row of legacyRows(prefix)) { + await sql.execute( + `INSERT INTO junior_memory_memories + (id, scope, scope_key, type, subject_type, subject_key, content, source_platform, source_key, idempotency_key, observed_at_ms, created_at_ms) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 1, 1)`, + [ + row.id, + row.scope, + row.scope_key, + row.type, + row.subject_type, + row.subject_key, + `Content for ${String(row.id)}`, + row.source_platform, + row.source_key, + row.idempotency_key, + ], + ); + } +} + +async function expectRow( + sql: JuniorSqlExecutor, + id: string, + expected: Record, +): Promise { + const [row] = await sql.query>( + `SELECT scope, scope_key, type, conversation_id, archived_at_ms FROM junior_memory_memories WHERE id = $1`, + [id], + ); + const problems: string[] = []; + for (const [key, value] of Object.entries(expected)) { + if (row?.[key] !== value) { + problems.push( + `${id}.${key}: expected ${String(value)}, got ${String(row?.[key])}`, + ); + } + } + return problems; +} + +async function verifyData( + sql: JuniorSqlExecutor, + prefix: number, +): Promise { + // After legacy 0011, a missing conversation_id is a current row. Keep it. + const conversationId = (value: string) => (prefix < 12 ? value : null); + const problems = [ + ...(await expectRow(sql, "memory-a", { + scope: "private", + scope_key: "user-1", + type: "preference", + conversation_id: conversationId("slack:C1:1.1"), + archived_at_ms: null, + })), + ...(await expectRow(sql, "memory-b", { + scope: "public", + scope_key: "public", + type: "knowledge", + conversation_id: conversationId("slack:C1:1.1"), + archived_at_ms: null, + })), + ...(await expectRow(sql, "memory-c", { + scope: "private", + scope_key: "user-1", + type: "procedure", + conversation_id: conversationId("local:conversation-1"), + archived_at_ms: null, + })), + ]; + const [event] = await sql.query<{ scope: string }>( + `SELECT payload->'content'->'memories'->0->>'scope' AS scope + FROM junior_conversation_events WHERE conversation_id = 'slack:C1:1.1'`, + ); + if (event?.scope !== "private") { + problems.push(`event scope: expected private, got ${String(event?.scope)}`); + } + return problems; +} + +async function withAdmin( + adminUrl: string, + callback: (client: pg.Client) => Promise, +): Promise { + const client = new pg.Client({ + application_name: APPLICATION_NAME, + connectionString: adminUrl, + }); + await client.connect(); + try { + return await callback(client); + } finally { + await client.end(); + } +} + +async function dropDatabase(adminUrl: string, name: string): Promise { + await withAdmin(adminUrl, (client) => + client.query(`DROP DATABASE IF EXISTS "${name}"`), + ); +} + +async function createDatabase(adminUrl: string, name: string): Promise { + await dropDatabase(adminUrl, name); + await withAdmin(adminUrl, (client) => + client.query(`CREATE DATABASE "${name}"`), + ); + const url = new URL(adminUrl); + url.pathname = `/${name}`; + return url.toString(); +} + +async function rehearse( + label: string, + adminUrl: string, + prepare: (sql: JuniorSqlExecutor) => Promise, + verifyPrefix: number | undefined, +): Promise<{ fingerprint: Fingerprint; problems: string[] }> { + const databaseName = `junior_memory_rehearsal_${label}`; + const url = await createDatabase(adminUrl, databaseName); + const sql = createPostgresJuniorSqlExecutor({ + applicationName: APPLICATION_NAME, + connectionString: url, + statementTimeoutMs: false, + }); + const problems: string[] = []; + try { + await prepare(sql); + const first = await migrateSchema(sql); + if (first.migrated === 0) { + problems.push("first upgrade applied nothing"); + } + if (verifyPrefix !== undefined) { + problems.push(...(await verifyData(sql, verifyPrefix))); + } + const before = JSON.stringify(await fingerprint(sql)); + const second = await migrateSchema(sql); + if (second.migrated !== 0) { + problems.push(`rerun applied ${second.migrated} migrations`); + } + if (JSON.stringify(await fingerprint(sql)) !== before) { + problems.push("rerun changed the schema"); + } + return { fingerprint: JSON.parse(before) as Fingerprint, problems }; + } finally { + await sql.close(); + await dropDatabase(adminUrl, databaseName); + } +} + +async function main(): Promise { + const adminUrl = process.env.DATABASE_URL; + if (!adminUrl) { + throw new Error("DATABASE_URL is required"); + } + const preMemoryCore = trimmedMigrationsDir( + CORE_MIGRATIONS, + (entry) => entry.tag !== MEMORY_MIGRATION_TAG, + ); + const results: Array<{ label: string; problems: string[] }> = []; + try { + const fresh = await rehearse("fresh", adminUrl, async () => {}, undefined); + results.push({ label: "fresh", problems: fresh.problems }); + const expected = JSON.stringify(fresh.fingerprint); + for (const prefix of LEGACY_PREFIXES) { + const legacyDir = trimmedMigrationsDir( + LEGACY_MIGRATIONS, + (entry) => entry.idx < prefix, + ); + try { + const result = await rehearse( + `legacy_${prefix}`, + adminUrl, + async (sql) => { + await sql.migrate({ + migrationsFolder: preMemoryCore, + migrationsTable: "__drizzle_junior_core", + }); + if (prefix > 0) { + await migratePluginSchemas(sql, [ + { dir: legacyDir, pluginName: "memory" }, + ]); + } + await seedLegacyData(sql, prefix); + }, + prefix > 0 ? prefix : undefined, + ); + if (JSON.stringify(result.fingerprint) !== expected) { + result.problems.push("final schema differs from a fresh database"); + } + results.push({ + label: `legacy prefix ${prefix}`, + problems: result.problems, + }); + } finally { + rmSync(legacyDir, { recursive: true, force: true }); + } + } + } finally { + rmSync(preMemoryCore, { recursive: true, force: true }); + } + let failed = false; + for (const result of results) { + const status = result.problems.length === 0 ? "ok" : "FAILED"; + failed ||= result.problems.length > 0; + console.log(`${status.padEnd(7)} ${result.label}`); + for (const problem of result.problems) { + console.log(` - ${problem}`); + } + } + if (failed) { + process.exitCode = 1; + } +} + +await main(); diff --git a/packages/junior/src/api.ts b/packages/junior/src/api.ts index 418586bab5..95a6dd32a7 100644 --- a/packages/junior/src/api.ts +++ b/packages/junior/src/api.ts @@ -5,6 +5,7 @@ import { createConversationRoutes } from "./api/conversations/routes"; import { createCodeRoutes } from "./api/code/routes"; import { jsonResponse } from "./api/http"; import { createLocationRoutes } from "./api/locations/routes"; +import { createMemoryRoutes, MEMORY_API_PREFIX } from "./api/memory/routes"; import { createPeopleRoutes } from "./api/people/routes"; import { createPersonalTokenRoutes } from "./api/personal-tokens/routes"; import { createUserPageRoutes } from "./api/user-pages/routes"; @@ -75,6 +76,7 @@ export function createJuniorApi(): Hono { app.route("/api/people", createPeopleRoutes()); app.route("/api/locations", createLocationRoutes()); app.route("/api/user-pages", createUserPageRoutes()); + app.route(MEMORY_API_PREFIX, createMemoryRoutes()); app.route("/api/automations", createAutomationRoutes()); app.route("/api/workspaces", createWorkspaceRoutes()); app.notFound(() => diff --git a/packages/junior/src/api/memory/routes.ts b/packages/junior/src/api/memory/routes.ts new file mode 100644 index 0000000000..db28db0e06 --- /dev/null +++ b/packages/junior/src/api/memory/routes.ts @@ -0,0 +1,72 @@ +import { Hono } from "hono"; +import { pluginApiRouteRequestContextSchema } from "@sentry/junior-plugin-api"; +import { getDb } from "@/chat/db"; +import { createMemoryApi } from "@/chat/memory/api"; +import { MEMORY_FEATURE_NAME } from "@/chat/memory/feature"; +import { createPluginConversationEventReader } from "@/chat/plugins/conversation-event-reader"; +import { createPluginConversationEventStats } from "@/chat/plugins/conversation-event-stats"; +import { getCoreFeatures } from "@/chat/plugins/core-features"; +import { jsonResponse } from "../http"; +import type { JuniorApiEnv } from "../route"; +import { apiErrorSchema } from "../schema/common"; +import { requireViewer } from "../viewer"; + +/** Mount path for the core Memory REST routes. */ +export const MEMORY_API_PREFIX = "/api/memory"; + +/** Strip the mount prefix so the Memory REST app sees its own paths. */ +function memoryRequest(request: Request): Request { + const url = new URL(request.url); + const pathname = url.pathname; + const nextPath = + pathname === MEMORY_API_PREFIX + ? "/" + : pathname.slice(MEMORY_API_PREFIX.length) || "/"; + url.pathname = nextPath.startsWith("/") ? nextPath : `/${nextPath}`; + return new Request(url, request); +} + +/** + * Authenticated Memory REST routes mounted at `/api/memory`. + * + * Personal API tokens reach the read routes; the dashboard only accepts a + * browser session for `DELETE`. + */ +export function createMemoryRoutes(): Hono { + const app = new Hono(); + + app.all("/*", requireViewer, async (context) => { + const memory = getCoreFeatures().find( + (feature) => feature.manifest.name === MEMORY_FEATURE_NAME, + ); + if (!memory?.hooks) { + return jsonResponse( + apiErrorSchema, + { error: "Memory is disabled." }, + { status: 404 }, + ); + } + const viewer = context.get("viewer"); + const api = createMemoryApi({ + conversationEvents: createPluginConversationEventReader(memory), + db: getDb(), + eventStats: createPluginConversationEventStats(memory), + users: { resolve: async () => viewer }, + }); + return await api.fetch( + memoryRequest(context.req.raw), + pluginApiRouteRequestContextSchema.parse({ + auth: { + user: { + email: viewer.email, + emailVerified: true, + ...(viewer.displayName ? { name: viewer.displayName } : undefined), + }, + }, + pluginName: MEMORY_FEATURE_NAME, + }), + ); + }); + + return app; +} diff --git a/packages/junior/src/app.ts b/packages/junior/src/app.ts index 10c6546c88..7dca4356cc 100644 --- a/packages/junior/src/app.ts +++ b/packages/junior/src/app.ts @@ -27,7 +27,9 @@ import { setExperimentalFeatures, type ExperimentalFeaturesConfig, } from "@/chat/experimental"; -import { setBriefsConfig } from "@/chat/briefs/registration"; +import { createCoreFeatures } from "@/chat/app/core-features"; +import type { MemoryOptions } from "@/chat/memory/feature"; +import { setCoreFeatures } from "@/chat/plugins/core-features"; import { getSandboxResourceConfig, setSandboxResourceConfig, @@ -147,6 +149,11 @@ export interface JuniorAppOptions extends BotModelConfig { }; /** Install-wide provider defaults. Unregistered `provider.key` entries warn at startup. */ configDefaults?: Record; + /** + * Long-term Memory: automatic recall, passive extraction, memory tools, the + * Memories page, and the memory REST routes. Enabled by default. + */ + memory?: MemoryOptions; /** Queue consumer wiring for the durable conversation worker. */ conversationWork?: ConversationWorkCallbackOptions; /** Direct plugin set override. Usually omitted when `juniorNitro()` uses a plugin module. */ @@ -515,6 +522,8 @@ function dashboardHostRoutePaths( "/api/conversations/*", "/api/locations", "/api/locations/*", + "/api/memory", + "/api/memory/*", "/api/people", "/api/people/*", "/api/personal-tokens", @@ -707,7 +716,9 @@ export async function createApp(options?: JuniorAppOptions): Promise { const previousSlackReactionConfig = getSlackReactionConfig(); const previousSandboxResources = getSandboxResourceConfig(); const previousExperimentalFeatures = getExperimentalFeatures(); - const previousBriefsConfig = setBriefsConfig(options?.briefs); + const previousCoreFeatures = setCoreFeatures( + createCoreFeatures({ briefs: options?.briefs, memory: options?.memory }), + ); const previousDashboardLinkOptions = setDashboardConversationLinkOptions(dashboard); const restoreRuntimeConfig = (): void => { @@ -718,7 +729,7 @@ export async function createApp(options?: JuniorAppOptions): Promise { setSlackReactionConfig(previousSlackReactionConfig); setSandboxResourceConfig(previousSandboxResources); setExperimentalFeatures(previousExperimentalFeatures); - setBriefsConfig(previousBriefsConfig); + setCoreFeatures(previousCoreFeatures); setDashboardConversationLinkOptions(previousDashboardLinkOptions); }; let pluginRoutes: PluginRouteRegistration[] = []; diff --git a/packages/junior/src/chat/README.md b/packages/junior/src/chat/README.md index 168cf0fa63..b20e2c5f6f 100644 --- a/packages/junior/src/chat/README.md +++ b/packages/junior/src/chat/README.md @@ -43,6 +43,8 @@ the Conversation Location without giving the Run Delivery to that Location. input. HTTP routes call this code. They do not own another Conversation or Turn. - `briefs/`: durable, versioned Conversation Brief generation and storage. +- `memory/`: long-term Memory storage, recall, extraction, tools, and views. + See `memory/README.md`. - `agent-dispatch/`: durable task and plugin dispatch authority, mailbox adaptation, and plugin-facing outcome projection. - `agent-invocations/`: durable parent/child bindings, delegated work, and diff --git a/packages/junior/src/chat/agent-dispatch/heartbeat.ts b/packages/junior/src/chat/agent-dispatch/heartbeat.ts index 2e032b32da..81c75655df 100644 --- a/packages/junior/src/chat/agent-dispatch/heartbeat.ts +++ b/packages/junior/src/chat/agent-dispatch/heartbeat.ts @@ -1,4 +1,4 @@ -import { getPlugins } from "@/chat/plugins/agent-hooks"; +import { getRegistrations } from "@/chat/plugins/agent-hooks"; import { logException, logInfo } from "@/chat/logging"; import { recoverConversationWork } from "@/chat/task-execution/heartbeat"; import { runScheduledAutomationHeartbeat } from "@/chat/scheduled-automations/heartbeat"; @@ -48,7 +48,7 @@ export async function runPluginHeartbeats(args: { nowMs: number; }): Promise { let count = 0; - for (const plugin of getPlugins()) { + for (const plugin of getRegistrations()) { const pluginName = plugin.manifest.name; if (count >= (args.limit ?? DEFAULT_PLUGIN_LIMIT)) { break; diff --git a/packages/junior/src/chat/app/core-features.ts b/packages/junior/src/chat/app/core-features.ts new file mode 100644 index 0000000000..b0721e9745 --- /dev/null +++ b/packages/junior/src/chat/app/core-features.ts @@ -0,0 +1,22 @@ +import type { PluginRegistration } from "@sentry/junior-plugin-api"; +import { briefsFeature } from "@/chat/briefs/task"; +import { createMemoryFeature, type MemoryOptions } from "@/chat/memory/feature"; + +export interface CoreFeatureOptions { + /** Generate a durable Brief after each completed Turn. Disabled by default. */ + briefs?: { enabled?: boolean }; + /** Long-term Memory recall, extraction, tools, and views. Enabled by default. */ + memory?: MemoryOptions; +} + +/** + * Build the core feature registrations for one app or CLI process. + * + * Every core feature is always present so stored events keep rendering. Each + * feature's options decide which runtime contributions it makes. + */ +export function createCoreFeatures( + options: CoreFeatureOptions = {}, +): PluginRegistration[] { + return [briefsFeature(options.briefs), createMemoryFeature(options.memory)]; +} diff --git a/packages/junior/src/chat/briefs/registration.ts b/packages/junior/src/chat/briefs/registration.ts deleted file mode 100644 index c1320dd3dd..0000000000 --- a/packages/junior/src/chat/briefs/registration.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { PluginRegistration } from "@sentry/junior-plugin-api"; -import { briefsTaskRegistration } from "./task"; - -type BriefsConfig = Readonly<{ enabled?: boolean }>; - -let configuredBriefs: BriefsConfig = {}; - -/** Replace app-level Brief settings and return the previous setting. */ -export function setBriefsConfig(config?: { enabled?: boolean }): BriefsConfig { - const previous = { ...configuredBriefs }; - configuredBriefs = config ? { ...config } : {}; - return previous; -} - -/** Return whether automatic Brief generation is enabled. */ -export function isBriefsEnabled(): boolean { - return configuredBriefs.enabled === true; -} - -/** Return enabled core task registrations. */ -export function coreTaskRegistrations(): PluginRegistration[] { - return isBriefsEnabled() ? [briefsTaskRegistration] : []; -} diff --git a/packages/junior/src/chat/briefs/task.ts b/packages/junior/src/chat/briefs/task.ts index 6e4f30128b..b5b98f9021 100644 --- a/packages/junior/src/chat/briefs/task.ts +++ b/packages/junior/src/chat/briefs/task.ts @@ -190,7 +190,7 @@ export async function updateConversationBrief( } /** Core task registration kept outside the installed plugin catalog. */ -export const briefsTaskRegistration: PluginRegistration = { +const briefsTaskRegistration: PluginRegistration = { manifest: { name: "briefs", displayName: "Briefs", @@ -218,3 +218,21 @@ export const briefsTaskRegistration: PluginRegistration = { }, }, }; + +/** + * Register Briefs as a core feature. + * + * Disabled Briefs keep only their event definitions so stored Brief events + * still render in transcripts. + */ +export function briefsFeature( + options: { enabled?: boolean } = {}, +): PluginRegistration { + if (options.enabled) { + return briefsTaskRegistration; + } + return { + manifest: briefsTaskRegistration.manifest, + conversationEvents: briefsTaskRegistration.conversationEvents, + }; +} diff --git a/packages/junior/src/chat/conversations/sql/migrations.ts b/packages/junior/src/chat/conversations/sql/migrations.ts index 979029770d..a219ed54b0 100644 --- a/packages/junior/src/chat/conversations/sql/migrations.ts +++ b/packages/junior/src/chat/conversations/sql/migrations.ts @@ -8,6 +8,8 @@ import { juniorSqlSchema as schema } from "@/db/schema"; const CORE_MIGRATION_BRIDGE_VERSION = "0.107.1"; const MIGRATIONS_TABLE = "__drizzle_junior_core"; +/** Postgres extensions the core schema needs. Memory owns both. */ +const REQUIRED_EXTENSIONS = ["btree_gin", "vector"] as const; /** Resolve the packaged Drizzle migration directory in source or built output. */ function migrationFolder(): string { @@ -102,6 +104,22 @@ function assertSupportedMigrationState(state: CoreMigrationState): void { ); } +/** Enable required extensions, or stop with an operator prerequisite error. */ +async function enableRequiredExtensions( + executor: JuniorSqlMigrationExecutor, +): Promise { + for (const extension of REQUIRED_EXTENSIONS) { + try { + await executor.execute(`CREATE EXTENSION IF NOT EXISTS ${extension}`); + } catch (error) { + throw new Error( + `Junior requires the Postgres extensions ${REQUIRED_EXTENSIONS.join(" and ")}, but this database could not enable "${extension}". Install pgvector and the btree_gin contrib extension on the database server, or enable them as a privileged user, then rerun \`junior upgrade\`.`, + { cause: error }, + ); + } + } +} + export { schema }; /** Apply the packaged Drizzle migrations during `junior upgrade`. */ @@ -127,6 +145,7 @@ export async function migrateSchema( return migrationResult(migrations, lockedState); } assertSupportedMigrationState(lockedState); + await enableRequiredExtensions(executor); await executor.migrate({ migrationsFolder, migrationsTable: MIGRATIONS_TABLE, diff --git a/packages/junior/src/chat/events/ingest.ts b/packages/junior/src/chat/events/ingest.ts index f2da9f62f7..a50b0a2667 100644 --- a/packages/junior/src/chat/events/ingest.ts +++ b/packages/junior/src/chat/events/ingest.ts @@ -42,6 +42,7 @@ export async function ingestEvent( const result = await enqueueEventNotification({ event, queue: options.queue, + receivedAtMs: nowMs, state: options.state, subscription: current, }); diff --git a/packages/junior/src/chat/events/notification.ts b/packages/junior/src/chat/events/notification.ts index 6da19e42d4..7be3eb17af 100644 --- a/packages/junior/src/chat/events/notification.ts +++ b/packages/junior/src/chat/events/notification.ts @@ -141,6 +141,8 @@ export function createEventInboundMessage(input: { export async function enqueueEventNotification(args: { event: EventNotification; queue: ConversationWorkQueue; + /** Ingest time. The burst window before the Turn runs starts here. */ + receivedAtMs: number; subscription: Watch; state?: Parameters[0]["state"]; }): Promise { @@ -180,6 +182,7 @@ export async function enqueueEventNotification(args: { conversationId: args.subscription.conversationId, id: args.subscription.id, }, + receivedAtMs: args.receivedAtMs, text, }), queue: args.queue, diff --git a/packages/junior/src/chat/events/runtime-catalog.ts b/packages/junior/src/chat/events/runtime-catalog.ts index 9f258c1182..6bc095fb0a 100644 --- a/packages/junior/src/chat/events/runtime-catalog.ts +++ b/packages/junior/src/chat/events/runtime-catalog.ts @@ -1,4 +1,4 @@ -import { getPlugins } from "@/chat/plugins/agent-hooks"; +import { getRegistrations } from "@/chat/plugins/agent-hooks"; import { CORE_EVENT_NAMESPACE, type EventCatalog } from "@/chat/events/catalog"; import { workspaceSnapshotEvents } from "@/chat/sandbox/snapshot/events"; @@ -12,7 +12,7 @@ export function getEventCatalog(): EventCatalog { const catalog: Record = { [CORE_EVENT_NAMESPACE]: workspaceSnapshotEvents(), }; - for (const plugin of getPlugins()) { + for (const plugin of getRegistrations()) { const registration = plugin.events; if (!registration || registration.isEnabled?.() === false) { continue; diff --git a/packages/junior-memory/README.md b/packages/junior/src/chat/memory/README.md similarity index 71% rename from packages/junior-memory/README.md rename to packages/junior/src/chat/memory/README.md index 6a69d06d10..be923b9c87 100644 --- a/packages/junior-memory/README.md +++ b/packages/junior/src/chat/memory/README.md @@ -1,14 +1,19 @@ -# @sentry/junior-memory +# Memory -The memory plugin stores durable facts, recalls relevant facts -into prompts, and learns candidates from completed sessions. SQL schemas, -exported types, tools, and tests are authoritative. +Memory is a Junior core feature. It stores durable facts, recalls relevant +facts into prompts, and learns candidates from completed sessions. SQL +schemas, exported types, tools, and tests are authoritative. + +`feature.ts` registers Memory through the core feature contract in +`../plugins/core-features.ts`. `createApp()` always installs it; the +`@sentry/junior/memory` entry exposes the supported API to apps. ## Surfaces - `createMemory`, `archiveMemory`, `listMemories`, and `searchMemories` are - model-visible tools registered by `src/plugin.ts`. `archiveMemory` can forget - public memory or private memory owned by the current User. + model-visible tools registered by `feature.ts` under the `memory` namespace. + `archiveMemory` can forget public memory or private memory owned by the + current User. - `userPrompt` recall contributes bounded memory context before a run. - `processSession` reviews completed sessions asynchronously for passive learning. @@ -20,10 +25,10 @@ exported types, tools, and tests are authoritative. `memory/memories_captured` events. The System plugin report uses the same event-cost feed. - Authenticated REST clients can list and search authorized memories through - `GET /api/plugins/memory/memories`, read one through - `GET /api/plugins/memory/memories/:id`, and forget an authorized private - memory through `DELETE /api/plugins/memory/memories/:id`. Public memory is - read-only in the dashboard and REST API. + `GET /api/memory/memories`, read one through `GET /api/memory/memories/:id`, + and forget an authorized private memory through + `DELETE /api/memory/memories/:id`. `../../api/memory/routes.ts` mounts these + routes. Public memory is read-only in the dashboard and REST API. ## Scope And Visibility @@ -43,8 +48,8 @@ exported types, tools, and tests are authoritative. ## Storage -- The Drizzle schema in `src/db/schema.ts` and generated migrations define the - database contract. +- The Drizzle schema in `../../db/schema/memory.ts` and the core migrations + define the database contract. - Records retain provenance, the optional origin `conversation_id`, lifecycle status, supersession relationships, and timestamps needed for review and deletion. - Embeddings are derived indexes, not independent memory authority. @@ -90,15 +95,18 @@ exported types, tools, and tests are authoritative. ## Configuration -- `AI_MEMORY_MODEL` or `memoryPlugin({ modelId })` selects the structured - review model. -- `memoryPlugin({ disableRecall: true })` disables automatic prompt recall. -- `memoryPlugin({ disableExtraction: true })` disables passive session - extraction. The two flags are independent and do not disable explicit memory - tools. +- `AI_MEMORY_MODEL` or `createApp({ memory: { modelId } })` selects the + structured review model. +- `createApp({ memory: { disableRecall: true } })` disables automatic prompt recall. +- `createApp({ memory: { disableExtraction: true } })` disables passive session + extraction. The two settings are independent and do not disable explicit + memory tools. +- `createApp({ memory: { enabled: false } })` is an emergency switch. It keeps + stored memory events rendering and removes every other Memory surface. - Automatic recall uses a fixed cosine distance cutoff of `0.45` (for `text-embedding-3-small`). Explicit search does not apply that cutoff. -- Generate schema changes with `pnpm --filter @sentry/junior-memory db:generate`. +- Generate schema changes with `pnpm --filter @sentry/junior db:generate`. -Follow `../../policies/data-redaction.md`, `../../policies/security.md`, and the -plugin contract in `../junior-plugin-api/README.md`. +Follow `../../../../policies/data-redaction.md`, +`../../../../policies/security.md`, and the registration contract in +`../../../junior-plugin-api/README.md`. diff --git a/packages/junior-memory/src/agent.ts b/packages/junior/src/chat/memory/agent.ts similarity index 100% rename from packages/junior-memory/src/agent.ts rename to packages/junior/src/chat/memory/agent.ts diff --git a/packages/junior-memory/src/api.ts b/packages/junior/src/chat/memory/api.ts similarity index 100% rename from packages/junior-memory/src/api.ts rename to packages/junior/src/chat/memory/api.ts diff --git a/packages/junior-memory/src/cli/format.ts b/packages/junior/src/chat/memory/cli/format.ts similarity index 93% rename from packages/junior-memory/src/cli/format.ts rename to packages/junior/src/chat/memory/cli/format.ts index c828c69930..460788fd39 100644 --- a/packages/junior-memory/src/cli/format.ts +++ b/packages/junior/src/chat/memory/cli/format.ts @@ -1,4 +1,4 @@ -import type { juniorMemoryMemories } from "../db/schema"; +import type { juniorMemoryMemories } from "@/db/schema"; function formatDate(ms: number | null): string { return ms === null ? "-" : new Date(ms).toISOString(); diff --git a/packages/junior-memory/src/cli/index.ts b/packages/junior/src/chat/memory/cli/index.ts similarity index 89% rename from packages/junior-memory/src/cli/index.ts rename to packages/junior/src/chat/memory/cli/index.ts index 4957f2dc34..cfed099bec 100644 --- a/packages/junior-memory/src/cli/index.ts +++ b/packages/junior/src/chat/memory/cli/index.ts @@ -2,7 +2,7 @@ import type { PluginCliCommandDefinition } from "@sentry/junior-plugin-api"; import { configureMemorySearchCommand } from "./search"; import { configureMemoryShowCommand } from "./show"; -/** Create the plugin-owned memory admin CLI command. */ +/** Create the `memory` admin CLI command. */ export function createMemoryCliCommand(): PluginCliCommandDefinition { return { name: "memory", diff --git a/packages/junior-memory/src/cli/search.ts b/packages/junior/src/chat/memory/cli/search.ts similarity index 96% rename from packages/junior-memory/src/cli/search.ts rename to packages/junior/src/chat/memory/cli/search.ts index 41bba8cfa7..90b24dd843 100644 --- a/packages/junior-memory/src/cli/search.ts +++ b/packages/junior/src/chat/memory/cli/search.ts @@ -4,7 +4,7 @@ import type { PluginCliActionContext, PluginCliHost, } from "@sentry/junior-plugin-api"; -import { juniorMemoryMemories } from "../db/schema"; +import { juniorMemoryMemories } from "@/db/schema"; import type { MemoryDb } from "../store"; import { MEMORY_SCOPES, type MemoryScope } from "../types"; import { formatMemory } from "./format"; @@ -86,7 +86,7 @@ async function runSearch( return 0; } -/** Wire the memory search admin subcommand under the plugin namespace. */ +/** Wire the memory search admin subcommand under `memory`. */ export function configureMemorySearchCommand( parent: Command, junior: PluginCliHost, diff --git a/packages/junior-memory/src/cli/show.ts b/packages/junior/src/chat/memory/cli/show.ts similarity index 95% rename from packages/junior-memory/src/cli/show.ts rename to packages/junior/src/chat/memory/cli/show.ts index 7d2ec6a9d9..dfc87515a0 100644 --- a/packages/junior-memory/src/cli/show.ts +++ b/packages/junior/src/chat/memory/cli/show.ts @@ -4,7 +4,7 @@ import type { PluginCliHost, } from "@sentry/junior-plugin-api"; import { eq } from "drizzle-orm"; -import { juniorMemoryMemories } from "../db/schema"; +import { juniorMemoryMemories } from "@/db/schema"; import type { MemoryDb } from "../store"; import { formatMemory } from "./format"; diff --git a/packages/junior-memory/src/events.ts b/packages/junior/src/chat/memory/events.ts similarity index 100% rename from packages/junior-memory/src/events.ts rename to packages/junior/src/chat/memory/events.ts diff --git a/packages/junior-memory/src/plugin.ts b/packages/junior/src/chat/memory/feature.ts similarity index 76% rename from packages/junior-memory/src/plugin.ts rename to packages/junior/src/chat/memory/feature.ts index d08e8aaf93..46c3c81afc 100644 --- a/packages/junior-memory/src/plugin.ts +++ b/packages/junior/src/chat/memory/feature.ts @@ -1,6 +1,5 @@ -import { defineJuniorPlugin } from "@sentry/junior-plugin-api"; +import type { PluginRegistration } from "@sentry/junior-plugin-api"; import { createMemoryAgent } from "./agent"; -import { createMemoryApi } from "./api"; import { createMemoryCliCommand } from "./cli"; import { createMemoryArchiveTool, @@ -24,15 +23,33 @@ import { createMemoryUserPage } from "./user-pages"; const MEMORY_MODEL_ENV = "AI_MEMORY_MODEL"; -export interface MemoryPluginOptions { - /** Disable automatic prompt recall while keeping explicit memory tools available. */ - disableRecall?: boolean; +/** Registration name for Memory. Plugins cannot claim it. */ +export const MEMORY_FEATURE_NAME = "memory"; + +const memoryManifest = { + name: MEMORY_FEATURE_NAME, + displayName: "Memory", + description: "Long-term Junior memory storage and recall", +}; + +export interface MemoryOptions { + /** + * Emergency switch. `false` removes memory tools, recall, extraction, views, + * routes, and CLI commands while stored memory events keep rendering. + */ + enabled?: boolean; /** Disable passive memory extraction from completed sessions. */ disableExtraction?: boolean; + /** + * Structured review model. Defaults to `AI_MEMORY_MODEL`, then the app's + * default model. + */ modelId?: string; + /** Disable automatic prompt recall. The explicit memory tools stay available. */ + disableRecall?: boolean; } -function memoryModelId(options: MemoryPluginOptions): string | undefined { +function memoryModelId(options: MemoryOptions): string | undefined { const explicitModelId = options.modelId?.trim(); if (explicitModelId) { return explicitModelId; @@ -85,24 +102,31 @@ function memoryCreateToolContext(ctx: { }; } -/** Register Junior's long-term memory plugin. */ -export function memoryPlugin(options: MemoryPluginOptions = {}) { +/** + * Register long-term Memory as a core feature. + * + * Memory uses the plugin registration contract so the host serves its tools, + * recall, extraction task, events, user page, CLI, and operational report + * through the shared runtime. Core mounts the REST routes in `api.ts`. + */ +export function createMemoryFeature( + options: MemoryOptions = {}, +): PluginRegistration { + const conversationEvents = [ + memoriesCapturedEventV1, + memoriesCapturedEvent, + memoriesRecalledEvent, + ]; + if (options.enabled === false) { + return { manifest: memoryManifest, conversationEvents }; + } const modelId = memoryModelId(options); - return defineJuniorPlugin({ - manifest: { - name: "memory", - displayName: "Memory", - description: "Long-term Junior memory storage and recall", - }, + return { + manifest: memoryManifest, model: modelId ? { structuredModelId: modelId } : { structuredModel: "default" }, - packageName: "@sentry/junior-memory", - conversationEvents: [ - memoriesCapturedEventV1, - memoriesCapturedEvent, - memoriesRecalledEvent, - ], + conversationEvents, cli: { commands: [createMemoryCliCommand()], }, @@ -128,14 +152,6 @@ export function memoryPlugin(options: MemoryPluginOptions = {}) { nowMs: ctx.nowMs, }); }, - apiRoutes(ctx) { - return createMemoryApi({ - conversationEvents: ctx.conversationEvents, - db: ctx.db as MemoryDb, - eventStats: ctx.eventStats, - users: ctx.users, - }); - }, tools(ctx) { const agent = createMemoryAgent(ctx.model); const context = memoryToolContext({ @@ -183,5 +199,5 @@ export function memoryPlugin(options: MemoryPluginOptions = {}) { } : undefined), }, - }); + }; } diff --git a/packages/junior-memory/src/operational-report.ts b/packages/junior/src/chat/memory/operational-report.ts similarity index 99% rename from packages/junior-memory/src/operational-report.ts rename to packages/junior/src/chat/memory/operational-report.ts index 51a791c643..c988409d80 100644 --- a/packages/junior-memory/src/operational-report.ts +++ b/packages/junior/src/chat/memory/operational-report.ts @@ -4,7 +4,7 @@ import type { } from "@sentry/junior-plugin-api"; import { and, eq, gt, isNull, or, sql } from "drizzle-orm"; import { z } from "zod"; -import { juniorMemoryEmbeddings, juniorMemoryMemories } from "./db/schema"; +import { juniorMemoryEmbeddings, juniorMemoryMemories } from "@/db/schema"; import type { MemoryDb } from "./store"; const DAY_MS = 24 * 60 * 60 * 1_000; diff --git a/packages/junior-memory/src/process-session.ts b/packages/junior/src/chat/memory/process-session.ts similarity index 98% rename from packages/junior-memory/src/process-session.ts rename to packages/junior/src/chat/memory/process-session.ts index 5eccd165bb..6abc358b43 100644 --- a/packages/junior-memory/src/process-session.ts +++ b/packages/junior/src/chat/memory/process-session.ts @@ -213,9 +213,9 @@ async function getTaskExtraction( } /** - * Extract and store memories from a completed session plugin task. + * Extract and store memories from a completed session task. * - * Memory owns learning after a run and reads only the plugin run data. + * Memory owns learning after a run and reads only the task's run data. * Explicit memory tools stay separate so retries do not reinterpret user * requests. */ diff --git a/packages/junior-memory/src/ranking.ts b/packages/junior/src/chat/memory/ranking.ts similarity index 96% rename from packages/junior-memory/src/ranking.ts rename to packages/junior/src/chat/memory/ranking.ts index 67649cd33a..f068d077d0 100644 --- a/packages/junior-memory/src/ranking.ts +++ b/packages/junior/src/chat/memory/ranking.ts @@ -81,7 +81,9 @@ export function rankMemoryMatches( ...(!existing.lexical && match.lexical ? { lexical: match.lexical } : undefined), - ...(!existing.vector && match.vector ? { vector: match.vector } : undefined), + ...(!existing.vector && match.vector + ? { vector: match.vector } + : undefined), }); } return [...byId.values()].sort((left, right) => { diff --git a/packages/junior-memory/src/recall.ts b/packages/junior/src/chat/memory/recall.ts similarity index 98% rename from packages/junior-memory/src/recall.ts rename to packages/junior/src/chat/memory/recall.ts index 438d6c9c3b..da7efce9ca 100644 --- a/packages/junior-memory/src/recall.ts +++ b/packages/junior/src/chat/memory/recall.ts @@ -174,7 +174,9 @@ export async function createMemoryPromptContributions( }); if (candidates.length === 0) { await emitRecallOutcome({ - ...(embeddingCostUsd !== undefined ? { costUsd: embeddingCostUsd } : undefined), + ...(embeddingCostUsd !== undefined + ? { costUsd: embeddingCostUsd } + : undefined), events: context.events, memories: [], }); diff --git a/packages/junior-memory/src/scope.ts b/packages/junior/src/chat/memory/scope.ts similarity index 100% rename from packages/junior-memory/src/scope.ts rename to packages/junior/src/chat/memory/scope.ts diff --git a/packages/junior-memory/src/store.ts b/packages/junior/src/chat/memory/store.ts similarity index 99% rename from packages/junior-memory/src/store.ts rename to packages/junior/src/chat/memory/store.ts index 9b4850db57..d2bb770218 100644 --- a/packages/junior-memory/src/store.ts +++ b/packages/junior/src/chat/memory/store.ts @@ -22,12 +22,10 @@ import { type SQL, } from "drizzle-orm"; import { cosineDistance } from "drizzle-orm/sql/functions"; -import type { PgDatabase } from "drizzle-orm/pg-core"; -import type { PgQueryResultHKT } from "drizzle-orm/pg-core/session"; import { z } from "zod"; import { getSourceKey } from "@sentry/junior-plugin-api"; -import * as memorySqlSchema from "./db/schema"; -import { juniorMemoryEmbeddings, juniorMemoryMemories } from "./db/schema"; +import type { JuniorDatabase } from "@/db/db"; +import { juniorMemoryEmbeddings, juniorMemoryMemories } from "@/db/schema"; import { rankMemoryMatches, type MemoryMatch } from "./ranking"; import { MEMORY_EMBEDDING_DIMENSIONS, @@ -78,7 +76,8 @@ const EMBEDDING_METRIC = "cosine"; */ const RECALL_MAX_VECTOR_DISTANCE = 0.45; -export type MemoryDb = PgDatabase; +/** Memory reads and writes use the shared Junior database. */ +export type MemoryDb = JuniorDatabase; interface MemoryEmbedding { model: string; @@ -1194,7 +1193,7 @@ export function createMemoryStore( return { created: false, memory: args.duplicate }; } - /** Persist a memory under the plugin-derived scope and subject. */ + /** Persist a memory under the scope and subject derived from the runtime context. */ async function createScopedMemory( rawInput: CreateMemoryInput, subjectType: ResolvedMemorySubject["subjectType"], diff --git a/packages/junior-memory/src/tools.ts b/packages/junior/src/chat/memory/tools.ts similarity index 99% rename from packages/junior-memory/src/tools.ts rename to packages/junior/src/chat/memory/tools.ts index 97da63068a..a1a1a2591d 100644 --- a/packages/junior-memory/src/tools.ts +++ b/packages/junior/src/chat/memory/tools.ts @@ -507,10 +507,7 @@ export function createMemoryArchiveTool(context: MemoryToolContext) { inputSchema: archiveMemoryInputSchema, outputSchema: memorySingleOutputSchema, execute: async (input) => { - const parsedInput = parseMemoryToolInput( - archiveMemoryInputSchema, - input, - ); + const parsedInput = parseMemoryToolInput(archiveMemoryInputSchema, input); const runtimeContext = await memoryRuntimeContext(context); const memory = await (async () => { try { diff --git a/packages/junior-memory/src/types.ts b/packages/junior/src/chat/memory/types.ts similarity index 66% rename from packages/junior-memory/src/types.ts rename to packages/junior/src/chat/memory/types.ts index 5393deb5c0..ae571ad2ea 100644 --- a/packages/junior-memory/src/types.ts +++ b/packages/junior/src/chat/memory/types.ts @@ -1,18 +1,21 @@ import { actorSchema, sourceSchema } from "@sentry/junior-plugin-api"; import { z } from "zod"; +import { + MEMORY_EMBEDDING_METRICS, + MEMORY_KINDS, + MEMORY_SCOPES, + MEMORY_SOURCE_PLATFORMS, + MEMORY_SUBJECT_TYPES, +} from "@/db/schema/memory"; -export const MEMORY_KINDS = ["preference", "procedure", "knowledge"] as const; - -export const MEMORY_SCOPES = ["private", "public"] as const; -export const MEMORY_SUBJECT_TYPES = [ - "user", - "conversation", - "general", -] as const; -// Durable attribution follows Source kind, including dashboard roots. -export const MEMORY_SOURCE_PLATFORMS = ["slack", "local", "web"] as const; -export const MEMORY_EMBEDDING_METRICS = ["cosine"] as const; -export const MEMORY_EMBEDDING_DIMENSIONS = 1536; +export { + MEMORY_EMBEDDING_DIMENSIONS, + MEMORY_EMBEDDING_METRICS, + MEMORY_KINDS, + MEMORY_SCOPES, + MEMORY_SOURCE_PLATFORMS, + MEMORY_SUBJECT_TYPES, +} from "@/db/schema/memory"; export type MemoryKind = (typeof MEMORY_KINDS)[number]; export type MemoryScope = (typeof MEMORY_SCOPES)[number]; diff --git a/packages/junior-memory/src/user-pages.ts b/packages/junior/src/chat/memory/user-pages.ts similarity index 97% rename from packages/junior-memory/src/user-pages.ts rename to packages/junior/src/chat/memory/user-pages.ts index 7dd21b6ca1..aff10bcd96 100644 --- a/packages/junior-memory/src/user-pages.ts +++ b/packages/junior/src/chat/memory/user-pages.ts @@ -66,7 +66,7 @@ export function createMemoryUserPage(): PluginUserPageDefinition { ? [ { confirmation: "Forget this memory?", - href: `/api/plugins/memory/memories/${encodeURIComponent(memory.id)}`, + href: `/api/memory/memories/${encodeURIComponent(memory.id)}`, label: "Forget", method: "DELETE" as const, tone: "danger" as const, diff --git a/packages/junior-memory/src/viewer.ts b/packages/junior/src/chat/memory/viewer.ts similarity index 99% rename from packages/junior-memory/src/viewer.ts rename to packages/junior/src/chat/memory/viewer.ts index 95b0093354..ced92e4c2f 100644 --- a/packages/junior-memory/src/viewer.ts +++ b/packages/junior/src/chat/memory/viewer.ts @@ -17,7 +17,7 @@ import { sql, } from "drizzle-orm"; import { z } from "zod"; -import { juniorMemoryEmbeddings, juniorMemoryMemories } from "./db/schema"; +import { juniorMemoryEmbeddings, juniorMemoryMemories } from "@/db/schema"; import { publicMemoryScope } from "./scope"; import { parseMemoryRow, type MemoryDb, type MemoryRecord } from "./store"; import { MEMORY_KINDS, type MemorySourcePlatform } from "./types"; @@ -438,4 +438,3 @@ export async function getMemoryTimelineHours( }; }); } - diff --git a/packages/junior/src/chat/plugins/README.md b/packages/junior/src/chat/plugins/README.md index a57093192d..e887a23fd4 100644 --- a/packages/junior/src/chat/plugins/README.md +++ b/packages/junior/src/chat/plugins/README.md @@ -12,6 +12,17 @@ This directory loads explicitly configured plugins and adapts their public metadata across YAML and JavaScript. - Runtime does not scan dependencies or arbitrary filesystem paths for plugins. +## Core Features + +- Core features such as Briefs register through the same runtime contract as + code plugins. `core-features.ts` installs them; `agent-hooks.ts` serves core + features and plugins together through `getRegistrations()`. +- Core features never enter plugin or package discovery. Their names are + reserved, and `validatePlugins` rejects a plugin that claims one. +- `chat/app/core-features.ts` builds the core feature list from app options. + Every core feature is always present so its stored events keep rendering; a + disabled feature contributes only its event definitions. + ## Host Ownership - Validate names, manifests, duplicate registrations, and capability conflicts diff --git a/packages/junior/src/chat/plugins/agent-hooks.ts b/packages/junior/src/chat/plugins/agent-hooks.ts index 9069fb01d3..2dc2cae173 100644 --- a/packages/junior/src/chat/plugins/agent-hooks.ts +++ b/packages/junior/src/chat/plugins/agent-hooks.ts @@ -57,7 +57,10 @@ import { z } from "zod"; import { workspaceRepoCheckoutPath } from "@/chat/workspaces/checkout-path"; import { listWorkspaceNamesByRepository } from "@/chat/workspaces/store"; import { createCodeChangePublisher } from "@/chat/code/publisher"; -import { coreTaskRegistrations } from "@/chat/briefs/registration"; +import { + getCoreFeatures, + isCoreFeatureName, +} from "@/chat/plugins/core-features"; /** Signal that a plugin intentionally denied a tool execution. */ export class PluginHookDeniedError extends Error { @@ -390,6 +393,11 @@ export function validatePlugins(plugins: PluginRegistration[]): void { `Plugin name "${name}" must be a lowercase plugin identifier`, ); } + if (isCoreFeatureName(name)) { + throw new Error( + `Plugin name "${name}" is reserved for a Junior core feature`, + ); + } if (seen.has(name)) { throw new Error(`Duplicate plugin name "${name}"`); } @@ -432,10 +440,15 @@ export function getPlugins(): PluginRegistration[] { return [...registeredPlugins]; } +/** Return core features and runtime hook plugins as one host registration list. */ +export function getRegistrations(): PluginRegistration[] { + return [...getCoreFeatures(), ...registeredPlugins]; +} + /** Apply plugin Markdown rewrites before destination delivery formatting. */ export function applyPluginFormatMarkdown(text: string): string { let transformed = text; - for (const plugin of getPlugins()) { + for (const plugin of getRegistrations()) { const hook = plugin.hooks?.formatMarkdown; if (!hook) { continue; @@ -462,7 +475,7 @@ export async function getPluginSystemPromptContributions( ): Promise { const contributions: PluginPromptContributionContext[] = []; let totalChars = 0; - for (const plugin of getPlugins()) { + for (const plugin of getRegistrations()) { const pluginName = plugin.manifest.name; const hook = plugin.hooks?.systemPrompt; if (!hook) { @@ -532,7 +545,7 @@ export async function getPluginUserPromptContributions(args: { const contributions: PluginPromptContributionContext[] = []; let totalChars = 0; let totalContextBytes = 0; - for (const plugin of getPlugins()) { + for (const plugin of getRegistrations()) { const pluginName = plugin.manifest.name; const hook = plugin.hooks?.userPrompt; if (!hook) { @@ -612,7 +625,7 @@ export function getPluginTools( sandbox: PluginSandbox = createSandboxCapability(context.workspace), ): Record { const tools: Record = {}; - for (const plugin of getPlugins()) { + for (const plugin of getRegistrations()) { const pluginName = plugin.manifest.name; const hook = plugin.hooks?.tools; if (!hook) { @@ -872,7 +885,7 @@ export function getPluginRoutes(options: { const seen = new Set(); const methodsByPath = new Map>(); - for (const plugin of getPlugins()) { + for (const plugin of getRegistrations()) { const pluginName = plugin.manifest.name; const hook = plugin.hooks?.routes; if (!hook) { @@ -991,7 +1004,7 @@ export function getPluginRoutes(options: { export function getPluginApiRoutes(): PluginApiRouteRegistration[] { const routes: PluginApiRouteRegistration[] = []; - for (const plugin of getPlugins()) { + for (const plugin of getRegistrations()) { const pluginName = plugin.manifest.name; const hook = plugin.hooks?.apiRoutes; if (!hook) { @@ -1049,7 +1062,7 @@ function trustedSlackConversationUrl( export function getPluginSlackConversationLink( conversationId: string, ): SlackConversationLink | undefined { - for (const plugin of getPlugins()) { + for (const plugin of getRegistrations()) { const pluginName = plugin.manifest.name; const hook = plugin.hooks?.slackConversationLink; if (!hook) { @@ -1356,7 +1369,7 @@ export async function getPluginOperationalReports( nowMs: number, ): Promise { const reports: PluginOperationalReport[] = []; - for (const plugin of [...coreTaskRegistrations(), ...getPlugins()]) { + for (const plugin of getRegistrations()) { const pluginName = plugin.manifest.name; const hook = plugin.hooks?.operationalReport; if (!hook) { @@ -1397,7 +1410,7 @@ export async function getPluginProfileReports(args: { viewer: User; }): Promise { const reports: PluginOperationalReport[] = []; - for (const plugin of getPlugins()) { + for (const plugin of getRegistrations()) { const pluginName = plugin.manifest.name; const hook = plugin.hooks?.profileReport; if (!hook) { @@ -1496,7 +1509,7 @@ export function createPluginHookRunner( actors?: () => Actor[]; } = {}, ): PluginHookRunner { - const loaded = getPlugins(); + const loaded = getRegistrations(); return { async afterMcpTool(tool) { diff --git a/packages/junior/src/chat/plugins/conversation-events.ts b/packages/junior/src/chat/plugins/conversation-events.ts index e5fa2388d7..8a58a80245 100644 --- a/packages/junior/src/chat/plugins/conversation-events.ts +++ b/packages/junior/src/chat/plugins/conversation-events.ts @@ -5,9 +5,8 @@ import type { PluginConversationEventValue, PluginRegistration, } from "@sentry/junior-plugin-api"; -import { briefsTaskRegistration } from "@/chat/briefs/task"; import { getConversationEventStore } from "@/chat/db"; -import { getPlugins } from "./agent-hooks"; +import { getRegistrations } from "./agent-hooks"; function registeredDefinition( plugin: PluginRegistration, @@ -80,7 +79,7 @@ export function renderPluginConversationEvent(args: { namespace: string; version: number; }): ConversationEventPresentation | undefined { - const plugin = [briefsTaskRegistration, ...getPlugins()].find( + const plugin = getRegistrations().find( (candidate) => candidate.manifest.name === args.namespace, ); const definition = plugin?.conversationEvents?.find( diff --git a/packages/junior/src/chat/plugins/conversation-sidebar.ts b/packages/junior/src/chat/plugins/conversation-sidebar.ts index b36f4830b9..52e2e6f533 100644 --- a/packages/junior/src/chat/plugins/conversation-sidebar.ts +++ b/packages/junior/src/chat/plugins/conversation-sidebar.ts @@ -5,7 +5,7 @@ import { } from "@sentry/junior-plugin-api"; import { getDb } from "@/chat/db"; import { logWarn } from "@/chat/logging"; -import { getPlugins } from "@/chat/plugins/agent-hooks"; +import { getRegistrations } from "@/chat/plugins/agent-hooks"; import { createPluginLogger } from "@/chat/plugins/logging"; /** Return plugin-selected sidebar annotations for candidate conversations. */ @@ -15,14 +15,14 @@ export async function listConversationSidebarAnnotations( ): Promise> { const candidates = new Set(conversationIds); const selected: Record = {}; - for (const plugin of getPlugins()) { + for (const plugin of getRegistrations()) { const hook = plugin.hooks?.conversationSidebar; if (!hook) continue; const annotationsByConversationId = Object.fromEntries( conversationIds.flatMap((conversationId) => { - const annotations = (annotationsByConversation.get(conversationId) ?? []).filter( - (annotation) => annotation.plugin === plugin.manifest.name, - ); + const annotations = ( + annotationsByConversation.get(conversationId) ?? [] + ).filter((annotation) => annotation.plugin === plugin.manifest.name); return annotations.length > 0 ? [[conversationId, annotations]] : []; }), ); @@ -38,7 +38,9 @@ export async function listConversationSidebarAnnotations( result.annotationsByConversationId, )) { if (!candidates.has(conversationId)) continue; - const parsed = conversationSidebarAnnotationSchema.array().safeParse(annotations); + const parsed = conversationSidebarAnnotationSchema + .array() + .safeParse(annotations); if (!parsed.success || parsed.data.length === 0) continue; selected[conversationId] = [ ...(selected[conversationId] ?? []), @@ -48,7 +50,8 @@ export async function listConversationSidebarAnnotations( } catch (error) { logWarn("plugin.conversation_sidebar.hook.failed", { "app.plugin.name": plugin.manifest.name, - "exception.message": error instanceof Error ? error.message : String(error), + "exception.message": + error instanceof Error ? error.message : String(error), }); } } diff --git a/packages/junior/src/chat/plugins/core-features.ts b/packages/junior/src/chat/plugins/core-features.ts new file mode 100644 index 0000000000..9c59a52da3 --- /dev/null +++ b/packages/junior/src/chat/plugins/core-features.ts @@ -0,0 +1,48 @@ +/** + * Core feature registrations. + * + * A core feature uses the same runtime registration contract as a plugin so + * the host serves its tools, prompt context, tasks, events, user pages, API + * routes, CLI commands, and operational reports through one path. Core + * features never enter plugin or package discovery, and no plugin can claim a + * core feature name. + */ +import type { PluginRegistration } from "@sentry/junior-plugin-api"; + +/** Registration names that core features own. */ +export const CORE_FEATURE_NAMES: ReadonlySet = new Set([ + "briefs", + "memory", +]); + +let registeredCoreFeatures: PluginRegistration[] = []; + +/** Return whether a registration name belongs to a core feature. */ +export function isCoreFeatureName(name: string): boolean { + return CORE_FEATURE_NAMES.has(name); +} + +/** Replace the installed core features and return the previous list for rollback. */ +export function setCoreFeatures( + features: PluginRegistration[], +): PluginRegistration[] { + const seen = new Set(); + for (const feature of features) { + const name = feature.manifest.name; + if (!isCoreFeatureName(name)) { + throw new Error(`Core feature name "${name}" is not reserved for core`); + } + if (seen.has(name)) { + throw new Error(`Duplicate core feature "${name}"`); + } + seen.add(name); + } + const previous = registeredCoreFeatures; + registeredCoreFeatures = [...features]; + return previous; +} + +/** Return the installed core features without exposing mutable state. */ +export function getCoreFeatures(): PluginRegistration[] { + return [...registeredCoreFeatures]; +} diff --git a/packages/junior/src/chat/plugins/registry.ts b/packages/junior/src/chat/plugins/registry.ts index 8b030dc90d..d123accf22 100644 --- a/packages/junior/src/chat/plugins/registry.ts +++ b/packages/junior/src/chat/plugins/registry.ts @@ -2,6 +2,7 @@ import { readFileSync, readdirSync, statSync } from "node:fs"; import path from "node:path"; import type { CredentialBroker } from "@/chat/credentials/broker"; import { pluginRoots } from "@/chat/discovery"; +import { isCoreFeatureName } from "@/chat/plugins/core-features"; import { logInfo, logWarn, setSpanAttributes } from "@/chat/logging"; import { parseInlinePluginManifest, parsePluginManifest } from "./manifest"; import { createOAuthBearerBroker } from "./auth/oauth-bearer-broker"; @@ -108,6 +109,11 @@ function registerPluginManifest( if (state.pluginsByName.has(manifest.name)) { throw new Error(`Duplicate plugin name "${manifest.name}"`); } + if (isCoreFeatureName(manifest.name)) { + throw new Error( + `Plugin name "${manifest.name}" is reserved for a Junior core feature`, + ); + } for (const domain of providerDomains(manifest)) { const owner = state.domainToPlugin.get(domain); @@ -511,7 +517,9 @@ export function createPluginCatalogRuntime(): PluginCatalogRuntime { commands.push({ cmd: command.cmd, ...(command.args ? { args: [...command.args] } : undefined), - ...(command.sudo !== undefined ? { sudo: command.sudo } : undefined), + ...(command.sudo !== undefined + ? { sudo: command.sudo } + : undefined), }); } } diff --git a/packages/junior/src/chat/plugins/task-runner.ts b/packages/junior/src/chat/plugins/task-runner.ts index a4f7262590..a4ddf2a3c1 100644 --- a/packages/junior/src/chat/plugins/task-runner.ts +++ b/packages/junior/src/chat/plugins/task-runner.ts @@ -47,8 +47,7 @@ import { getTurnRecord, type TurnRecord, } from "@/chat/task-execution/checkpoint"; -import { coreTaskRegistrations } from "@/chat/briefs/registration"; -import { getPlugins } from "./agent-hooks"; +import { getRegistrations } from "./agent-hooks"; import { pluginTaskId, pluginTaskParamsSchema, @@ -442,7 +441,7 @@ function taskPluginContext( } function findPluginTask(message: PluginTaskQueueMessage) { - const plugin = [...coreTaskRegistrations(), ...getPlugins()].find( + const plugin = getRegistrations().find( (candidate) => candidate.manifest.name === message.plugin, ); if (!plugin?.tasks || !Object.hasOwn(plugin.tasks, message.name)) { @@ -458,10 +457,7 @@ export async function scheduleSessionCompletedPluginTasks( options: ScheduleSessionCompletedPluginTasksOptions = {}, ): Promise { const coreParams = pluginTaskParamsSchema.parse(params); - const taskRegistrations = [ - ...getPlugins(), - ...coreTaskRegistrations(), - ].flatMap((plugin) => + const taskRegistrations = getRegistrations().flatMap((plugin) => Object.keys(plugin.tasks ?? {}).map((name) => ({ name, plugin })), ); if (taskRegistrations.length === 0) { diff --git a/packages/junior/src/chat/plugins/unfinished-work.ts b/packages/junior/src/chat/plugins/unfinished-work.ts index 3b1b989386..8c5942d5ef 100644 --- a/packages/junior/src/chat/plugins/unfinished-work.ts +++ b/packages/junior/src/chat/plugins/unfinished-work.ts @@ -1,5 +1,5 @@ import { getDb } from "@/chat/db"; -import { getPlugins } from "@/chat/plugins/agent-hooks"; +import { getRegistrations } from "@/chat/plugins/agent-hooks"; import { createPluginLogger } from "@/chat/plugins/logging"; import { logWarn } from "@/chat/logging"; @@ -24,7 +24,7 @@ export async function listConversationWork( const assigned = new Set(); const finishedAtById = new Map(); const unfinished = new Set(); - for (const plugin of getPlugins()) { + for (const plugin of getRegistrations()) { const hook = plugin.hooks?.unfinishedWork; if (!hook) continue; try { diff --git a/packages/junior/src/chat/plugins/user-pages.ts b/packages/junior/src/chat/plugins/user-pages.ts index 2573fc4324..6776e7eea8 100644 --- a/packages/junior/src/chat/plugins/user-pages.ts +++ b/packages/junior/src/chat/plugins/user-pages.ts @@ -14,12 +14,13 @@ import { import { getDb } from "@/chat/db"; import { createPluginLogger } from "@/chat/plugins/logging"; import { resolveViewerUser } from "@/chat/plugins/viewer"; -import { getPlugins } from "@/chat/plugins/agent-hooks"; +import { getRegistrations } from "@/chat/plugins/agent-hooks"; +import { isCoreFeatureName } from "@/chat/plugins/core-features"; /** List safe navigation metadata for registered plugin user pages. */ export function readPluginUserPageLinks(): PluginUserPageLink[] { return pluginUserPageLinksSchema.parse( - getPlugins().flatMap((plugin) => + getRegistrations().flatMap((plugin) => (plugin.userPages ?? []).map((page) => ({ description: page.description, id: page.id, @@ -32,12 +33,16 @@ export function readPluginUserPageLinks(): PluginUserPageLink[] { ); } +/** Core features own `/api//`; plugins own `/api/plugins//`. */ function actionBelongsToPlugin(href: string, pluginName: string): boolean { const parsed = new URL(href, "http://junior.local"); + const prefix = isCoreFeatureName(pluginName) + ? `/api/${pluginName}/` + : `/api/plugins/${pluginName}/`; return ( parsed.origin === "http://junior.local" && parsed.pathname === href && - parsed.pathname.startsWith(`/api/plugins/${pluginName}/`) && + parsed.pathname.startsWith(prefix) && !parsed.search && !parsed.hash ); @@ -50,7 +55,7 @@ export async function readPluginUserPage(input: { pluginName: string; query: PluginUserPageInput; }): Promise { - const plugin = getPlugins().find( + const plugin = getRegistrations().find( (candidate) => candidate.manifest.name === input.pluginName, ); const page = plugin?.userPages?.find( diff --git a/packages/junior/src/chat/task-execution/conversation-turn.ts b/packages/junior/src/chat/task-execution/conversation-turn.ts index dacd6e0c55..c5cc7977c4 100644 --- a/packages/junior/src/chat/task-execution/conversation-turn.ts +++ b/packages/junior/src/chat/task-execution/conversation-turn.ts @@ -88,8 +88,8 @@ import { isEventConversationMessage } from "@/chat/events/actor"; /** Extra debounce time added per additional event already batched. */ const EVENT_WAIT_PER_EXTRA_MESSAGE_MS = 5_000; -/** Upper bound on the debounce window regardless of batch size. */ -const EVENT_MAX_WAIT_MS = 60_000; +/** Upper bound on the event burst window regardless of batch size. */ +export const EVENT_MAX_WAIT_MS = 60_000; function stableHex(...parts: string[]): string { return createHash("sha256") diff --git a/packages/junior/src/cli/chat.ts b/packages/junior/src/cli/chat.ts index 83ded0fbc2..ac722eec70 100644 --- a/packages/junior/src/cli/chat.ts +++ b/packages/junior/src/cli/chat.ts @@ -240,8 +240,9 @@ async function prepareLocalChatRun( // subagents here so spawnAgent matches the wired child-worker path. const { setExperimentalFeatures } = await import("@/chat/experimental"); setExperimentalFeatures({ subagents: true }); - const { setBriefsConfig } = await import("@/chat/briefs/registration"); - setBriefsConfig({ enabled: true }); + const { createCoreFeatures } = await import("@/chat/app/core-features"); + const { setCoreFeatures } = await import("@/chat/plugins/core-features"); + setCoreFeatures(createCoreFeatures({ briefs: { enabled: true } })); const { runLocalAgentTurn } = await import("@/chat/local/runner"); const { startLocalOAuthCallbackServer } = await import("@/chat/local/oauth-callback-server"); diff --git a/packages/junior/src/cli/init.ts b/packages/junior/src/cli/init.ts index da6ada8174..3c375476e0 100644 --- a/packages/junior/src/cli/init.ts +++ b/packages/junior/src/cli/init.ts @@ -41,12 +41,8 @@ function writePluginsFile(targetDir: string): void { fs.writeFileSync( path.join(targetDir, "plugins.ts"), `import { defineJuniorPlugins } from "@sentry/junior"; -import { memoryPlugin } from "@sentry/junior-memory"; -export const plugins = defineJuniorPlugins([ - memoryPlugin(), - "@sentry/junior-maintenance", -]); +export const plugins = defineJuniorPlugins(["@sentry/junior-maintenance"]); `, ); } @@ -182,7 +178,6 @@ export async function runInit( "@opentelemetry/api": "1.9.1", "@opentelemetry/core": "2.9.0", "@sentry/junior": "latest", - "@sentry/junior-memory": "latest", "@sentry/junior-maintenance": "latest", "@sentry/nitro": "10.65.0", hono: "^4.12.27", diff --git a/packages/junior/src/cli/plugins.ts b/packages/junior/src/cli/plugins.ts index 206dbd6360..421080e71d 100644 --- a/packages/junior/src/cli/plugins.ts +++ b/packages/junior/src/cli/plugins.ts @@ -20,6 +20,7 @@ import { validatePluginEgressCredentialHooks, validatePluginRegistrations, } from "@/chat/plugins/validation"; +import { createCoreFeatures } from "@/chat/app/core-features"; import { loadAppPluginSet } from "@/plugin-module"; import { pluginCliRegistrationsFromPluginSet, @@ -226,11 +227,18 @@ async function loadPluginRegistrations(args: { runtimePlugins: PluginRegistration[]; }> { const pluginSet = args.pluginSet; + const coreCliFeatures = createCoreFeatures().filter((feature) => + Boolean(feature.cli), + ); if (!pluginSet) { - return { cliPlugins: [], runtimePlugins: [] }; + args.validateConfiguredCommands?.(coreCliFeatures); + return { cliPlugins: coreCliFeatures, runtimePlugins: [] }; } - const cliPlugins = pluginCliRegistrationsFromPluginSet(pluginSet); + const cliPlugins = [ + ...coreCliFeatures, + ...pluginCliRegistrationsFromPluginSet(pluginSet), + ]; const runtimePlugins = pluginRuntimeRegistrationsFromPluginSet(pluginSet); const pluginConfig = pluginCatalogConfigFromPluginSet(pluginSet); validatePlugins(runtimePlugins); diff --git a/packages/junior/src/db/schema.ts b/packages/junior/src/db/schema.ts index 23d176bcf9..fcbf1bcf39 100644 --- a/packages/junior/src/db/schema.ts +++ b/packages/junior/src/db/schema.ts @@ -14,6 +14,7 @@ import { juniorAgentInvocations, } from "./schema/agent-invocations"; import { juniorLocationConfigurations } from "./schema/location-configurations"; +import { juniorMemoryEmbeddings, juniorMemoryMemories } from "./schema/memory"; import { juniorDestinations } from "./schema/destinations"; import { juniorEventAutomations } from "./schema/event-automations"; import { juniorIdentities } from "./schema/identities"; @@ -46,6 +47,8 @@ export { juniorDestinations, juniorEventAutomations, juniorIdentities, + juniorMemoryEmbeddings, + juniorMemoryMemories, juniorSnapshots, juniorStats, juniorAutomationExecutions, @@ -75,6 +78,8 @@ export const juniorSqlSchema = { juniorDestinations, juniorEventAutomations, juniorIdentities, + juniorMemoryEmbeddings, + juniorMemoryMemories, juniorSnapshots, juniorStats, juniorAutomationExecutions, diff --git a/packages/junior-memory/src/db/schema.ts b/packages/junior/src/db/schema/memory.ts similarity index 87% rename from packages/junior-memory/src/db/schema.ts rename to packages/junior/src/db/schema/memory.ts index 048d0dc9eb..085cd4cb8f 100644 --- a/packages/junior-memory/src/db/schema.ts +++ b/packages/junior/src/db/schema/memory.ts @@ -1,8 +1,8 @@ /** - * Drizzle source of truth for memory plugin SQL migrations. + * Long-term Memory storage contract: memory rows and their derived embeddings. * - * Update this schema first, then regenerate packaged migrations with - * `pnpm --filter @sentry/junior-memory db:generate`. + * Update this schema first, then regenerate core migrations with + * `pnpm --filter @sentry/junior db:generate`. */ import { sql } from "drizzle-orm"; import { @@ -16,14 +16,18 @@ import { uniqueIndex, vector, } from "drizzle-orm/pg-core"; -import { - MEMORY_EMBEDDING_DIMENSIONS, - MEMORY_EMBEDDING_METRICS, - MEMORY_SCOPES, - MEMORY_SOURCE_PLATFORMS, - MEMORY_SUBJECT_TYPES, - MEMORY_KINDS, -} from "../types"; + +export const MEMORY_KINDS = ["preference", "procedure", "knowledge"] as const; +export const MEMORY_SCOPES = ["private", "public"] as const; +export const MEMORY_SUBJECT_TYPES = [ + "user", + "conversation", + "general", +] as const; +/** Durable attribution follows Source kind, including dashboard roots. */ +export const MEMORY_SOURCE_PLATFORMS = ["slack", "local", "web"] as const; +export const MEMORY_EMBEDDING_METRICS = ["cosine"] as const; +export const MEMORY_EMBEDDING_DIMENSIONS = 1536; const tsvector = customType<{ data: string }>({ dataType() { diff --git a/packages/junior/src/memory.ts b/packages/junior/src/memory.ts new file mode 100644 index 0000000000..42c8791e66 --- /dev/null +++ b/packages/junior/src/memory.ts @@ -0,0 +1,31 @@ +/** + * Public Memory API. + * + * Apps import Memory stores, schemas, and types from `@sentry/junior/memory`. + * `createApp()` registers Memory itself; configure it with + * `createApp({ memory })`. + */ +export type { MemoryOptions } from "@/chat/memory/feature"; +export { + memoryApiSchema, + memoryDashboardResponseSchema, + memoryListResponseSchema, + type MemoryApi, + type MemoryDashboardResponse, + type MemoryListResponse, +} from "@/chat/memory/api"; +export { createMemoryStore } from "@/chat/memory/store"; +export type { + ArchiveMemoryInput, + CreateMemoryInput, + CreateMemoryResult, + ListMemoriesInput, + MemoryDb, + MemoryEmbeddingProvider, + MemoryRecord, + MemoryStore, + MemoryStoreOptions, + SearchMemoriesInput, +} from "@/chat/memory/store"; +export { MEMORY_KINDS } from "@/chat/memory/types"; +export type { MemoryKind, MemoryRuntimeContext } from "@/chat/memory/types"; diff --git a/packages/junior/src/plugins.ts b/packages/junior/src/plugins.ts index 3f55d4144c..d6ed7e6084 100644 --- a/packages/junior/src/plugins.ts +++ b/packages/junior/src/plugins.ts @@ -52,7 +52,9 @@ function cloneInlineManifests( } : undefined), }, - ...(plugin.packageName ? { packageName: plugin.packageName } : undefined), + ...(plugin.packageName + ? { packageName: plugin.packageName } + : undefined), }, ] : [], @@ -81,6 +83,27 @@ function assertUniquePackageNames(packageNames: string[]): void { } } +const LEGACY_MEMORY_PACKAGE = "@sentry/junior-memory"; + +/** + * Stop startup when a plugin set still names the removed Memory plugin. + * + * Memory ships inside `@sentry/junior`. The error tells the operator how to + * move the old registration and its options. + */ +function assertNoLegacyMemory(inputs: JuniorPluginInput[]): void { + const legacy = inputs.some((input) => + typeof input === "string" + ? input === LEGACY_MEMORY_PACKAGE + : input.packageName === LEGACY_MEMORY_PACKAGE, + ); + if (legacy) { + throw new Error( + `${LEGACY_MEMORY_PACKAGE} was removed: Memory ships inside @sentry/junior. Remove memoryPlugin() or "${LEGACY_MEMORY_PACKAGE}" from your plugin set and JUNIOR_PLUGIN_PACKAGES, uninstall the package, and move its options to createApp({ memory }).`, + ); + } +} + function normalizePluginInput(input: JuniorPluginInput): { packageName?: string; registration?: PluginRegistration; @@ -96,6 +119,7 @@ export function defineJuniorPlugins( inputs: JuniorPluginInput[], options: JuniorPluginSetOptions = {}, ): JuniorPluginSet { + assertNoLegacyMemory(inputs); const normalized = inputs.map(normalizePluginInput); const packageNames = normalized.flatMap((input) => input.packageName ? [input.packageName] : [], @@ -169,6 +193,7 @@ function readEnvPluginPackages( ); } + assertNoLegacyMemory(parsed); return parsed; } diff --git a/packages/junior/tests/README.md b/packages/junior/tests/README.md index 80c2fa2bd6..6928a46693 100644 --- a/packages/junior/tests/README.md +++ b/packages/junior/tests/README.md @@ -24,6 +24,11 @@ through real wiring or remove it when stronger coverage owns the behavior. A test that needs internal module replacement, or a non-Slack/non-LLM external fake, belongs in `component/` unless it is rewritten to use real wiring. +Tests that run Turns through the app use `createTestApp()` from +`fixtures/app.ts`. It turns Memory off, because Memory adds recall and +extraction model calls to every Turn. Memory tests turn it on. Tests about +`createApp()` itself call it directly. + Every schema passed to `completeObject` must pass `strictProviderSchemaProblems` from `@sentry/junior-testing/structured-output` in its owning unit test. Strict structured-output providers reject optional diff --git a/packages/junior/tests/component/briefs/conversation-brief-task.test.ts b/packages/junior/tests/component/briefs/conversation-brief-task.test.ts index c33fb11566..f4762a298d 100644 --- a/packages/junior/tests/component/briefs/conversation-brief-task.test.ts +++ b/packages/junior/tests/component/briefs/conversation-brief-task.test.ts @@ -13,7 +13,8 @@ import { type CodeChangeInput, } from "@sentry/junior-plugin-api"; import type { PiMessage } from "@/chat/pi/messages"; -import { setBriefsConfig } from "@/chat/briefs/registration"; +import { briefsFeature } from "@/chat/briefs/task"; +import { setCoreFeatures } from "@/chat/plugins/core-features"; import { migrateSchema } from "@/chat/conversations/sql/migrations"; import { juniorConversationBriefs, @@ -193,7 +194,7 @@ describe("Conversation Brief task", () => { let fixture: LocalJuniorSqlFixture; beforeEach(async () => { - setBriefsConfig({ enabled: true }); + setCoreFeatures([briefsFeature({ enabled: true })]); process.env.JUNIOR_STATE_ADAPTER = "memory"; fixture = await createJuniorSqlFixture(); TEST.sql = fixture.sql; @@ -202,7 +203,7 @@ describe("Conversation Brief task", () => { }); afterEach(async () => { - setBriefsConfig(undefined); + setCoreFeatures([]); const { closeDb } = await import("@/chat/db"); const { disconnectStateAdapter } = await import("@/chat/state/adapter"); await disconnectStateAdapter(); diff --git a/packages/junior/tests/component/events/events.test.ts b/packages/junior/tests/component/events/events.test.ts index 18612e6afa..b21f5aaedf 100644 --- a/packages/junior/tests/component/events/events.test.ts +++ b/packages/junior/tests/component/events/events.test.ts @@ -3,7 +3,8 @@ import { createMemoryState } from "@chat-adapter/state-memory"; import { githubPlugin } from "@sentry/junior-github"; import type { StateAdapter } from "chat"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { createApp, defineJuniorPlugins } from "@/app"; +import { defineJuniorPlugins } from "@/app"; +import { createTestApp } from "../../fixtures/app"; import { getDispatchRecord } from "@/chat/agent-dispatch/store"; import { closeDb, getDb } from "@/chat/db"; import { @@ -324,7 +325,7 @@ describe("event delivery", () => { .update(body) .digest("hex")}`; - const app = await createApp({ + const app = await createTestApp({ conversationWork: { queue, run: async () => ({ status: "completed" }), diff --git a/packages/junior/tests/component/memory-plugin-storage.test.ts b/packages/junior/tests/component/memory/host-wiring.test.ts similarity index 56% rename from packages/junior/tests/component/memory-plugin-storage.test.ts rename to packages/junior/tests/component/memory/host-wiring.test.ts index 7af60c9e98..b4999e7213 100644 --- a/packages/junior/tests/component/memory-plugin-storage.test.ts +++ b/packages/junior/tests/component/memory/host-wiring.test.ts @@ -1,28 +1,20 @@ -import path from "node:path"; -import { readdirSync } from "node:fs"; import { afterAll, afterEach, describe, expect, it, vi } from "vitest"; import { - memoryPlugin, - createMemoryStore, - type MemoryDb, -} from "@sentry/junior-memory"; -import { defineJuniorPlugins } from "@/plugins"; -import { getPluginTools, setPlugins } from "@/chat/plugins/agent-hooks"; -import { migratePluginSchemas } from "@/chat/plugins/migrations"; -import { readMigrationFiles } from "drizzle-orm/migrator"; + createSlackSource, + defineJuniorPlugin, + PluginToolInputError, +} from "@sentry/junior-plugin-api"; +import { createMemoryFeature } from "@/chat/memory/feature"; +import { createMemoryStore, type MemoryDb } from "@/chat/memory/store"; +import { defineJuniorPlugins, pluginCatalogConfigFromEnv } from "@/plugins"; +import { getPluginTools } from "@/chat/plugins/agent-hooks"; +import { setCoreFeatures } from "@/chat/plugins/core-features"; import { closeDb } from "@/chat/db"; import { migrateSchema } from "@/chat/conversations/sql/migrations"; import { createSqlStore } from "@/chat/conversations/sql/store"; import { readActorIdentity, resolveViewerUser } from "@/chat/plugins/viewer"; import { readPluginUserPage } from "@/chat/plugins/user-pages"; -import { migratePluginsToSql } from "@/cli/upgrade/migrations/plugin-sql"; -import { runUpgrade } from "@/cli/upgrade"; -import { createEmptyJuniorSqlFixture } from "../fixtures/sql"; -import { - createSlackSource, - defineJuniorPlugin, - PluginToolInputError, -} from "@sentry/junior-plugin-api"; +import { createEmptyJuniorSqlFixture } from "../../fixtures/sql"; const NEON = vi.hoisted(() => ({ sql: undefined as @@ -73,7 +65,8 @@ vi.mock("@/chat/pi/client", () => ({ })); afterEach(async () => { - setPlugins([]); + setCoreFeatures([]); + vi.restoreAllMocks(); await closeDb(); NEON.sql = undefined; }); @@ -86,27 +79,6 @@ afterAll(() => { } }); -function memoryMigrationsDir(): string { - return path.resolve(process.cwd(), "../junior-memory/migrations"); -} - -function memoryMigrationFiles(): string[] { - return readdirSync(memoryMigrationsDir()) - .filter((filename) => filename.endsWith(".sql")) - .sort(); -} - -async function migrateMemorySchema( - fixture: Awaited>, -) { - await migratePluginSchemas(fixture.sql, [ - { - dir: memoryMigrationsDir(), - pluginName: "memory", - }, - ]); -} - async function recordPrivateConversation( fixture: Awaited>, args: { @@ -182,211 +154,38 @@ function memoryToolsFor(args: { }); } -describe("memory plugin host wiring", () => { - it("adopts exact legacy migration hashes without replaying them", async () => { - const fixture = await createEmptyJuniorSqlFixture(); - const migrations = readMigrationFiles({ - migrationsFolder: memoryMigrationsDir(), +describe("memory core feature host wiring", () => { + it("stops startup while a plugin set still names the removed Memory plugin", () => { + const removed = "@sentry/junior-memory was removed"; + // The last published `memoryPlugin()` returns this registration. + const publishedMemoryPlugin = defineJuniorPlugin({ + manifest: { + description: "Long-term Junior memory storage and recall", + displayName: "Memory", + name: "memory", + }, + packageName: "@sentry/junior-memory", }); - const migrationFiles = memoryMigrationFiles(); - expect(migrationFiles).toHaveLength(migrations.length); - - try { - await migrateMemorySchema(fixture); - const [migrationTable] = await fixture.sql.query<{ tablename: string }>(` -SELECT tablename -FROM pg_tables -WHERE schemaname = 'drizzle' - AND tablename LIKE '__drizzle_memory_%' -`); - expect(migrationTable).toBeDefined(); - await fixture.sql.execute( - `DROP TABLE drizzle.${migrationTable!.tablename}`, - ); - await fixture.sql.execute(` -CREATE TABLE junior_schema_migrations ( - id TEXT PRIMARY KEY, - checksum TEXT NOT NULL, - applied_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -) -`); - for (const [index, migration] of migrations.entries()) { - await fixture.sql.execute( - `INSERT INTO junior_schema_migrations (id, checksum) VALUES ($1, $2)`, - [`plugin:memory/${migrationFiles[index]}`, migration.hash], - ); - } - await expect( - migratePluginSchemas(fixture.sql, [ - { - dir: memoryMigrationsDir(), - pluginName: "memory", - }, - ]), - ).resolves.toEqual({ - existing: migrations.length, - migrated: 0, - scanned: migrations.length, - }); - } finally { - await fixture.close(); - } - }); - - it("does not adopt an unknown memory legacy checksum", async () => { - const fixture = await createEmptyJuniorSqlFixture(); - const migrationCount = readMigrationFiles({ - migrationsFolder: memoryMigrationsDir(), - }).length; - const [baselineFile] = memoryMigrationFiles(); - expect(baselineFile).toBeDefined(); - - try { - await fixture.sql.execute(` -CREATE TABLE junior_schema_migrations ( - id TEXT PRIMARY KEY, - checksum TEXT NOT NULL, - applied_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -) -`); - await fixture.sql.execute( - `INSERT INTO junior_schema_migrations (id, checksum) VALUES ($1, $2)`, - [`plugin:memory/${baselineFile}`, "unknown-memory-checksum"], - ); - - await expect( - migratePluginSchemas(fixture.sql, [ - { - dir: memoryMigrationsDir(), - pluginName: "memory", - }, - ]), - ).resolves.toEqual({ - existing: 0, - migrated: migrationCount, - scanned: migrationCount, - }); - } finally { - await fixture.close(); - } + expect(() => defineJuniorPlugins([publishedMemoryPlugin])).toThrow(removed); + expect(() => defineJuniorPlugins(["@sentry/junior-memory"])).toThrow( + removed, + ); + expect(() => + pluginCatalogConfigFromEnv({ + JUNIOR_PLUGIN_PACKAGES: JSON.stringify(["@sentry/junior-memory"]), + }), + ).toThrow(removed); }); - it("applies packaged migrations through plugin discovery", async () => { - const fixture = await createEmptyJuniorSqlFixture(); - NEON.sql = fixture.sql; - - try { - const migrationCount = readMigrationFiles({ - migrationsFolder: memoryMigrationsDir(), - }).length; - await expect( - migratePluginsToSql({ - pluginSet: defineJuniorPlugins([memoryPlugin()]), - sqlExecutor: fixture.sql, - }), - ).resolves.toEqual({ - existing: 0, - migrated: migrationCount, - scanned: migrationCount, - }); - - await expect( - fixture.sql.query<{ table_name: string }>( - ` -SELECT table_name -FROM information_schema.tables -WHERE table_name = 'junior_memory_memories' -`, - ), - ).resolves.toEqual([{ table_name: "junior_memory_memories" }]); - - await expect( - fixture.sql.query<{ extname: string }>( - "SELECT extname FROM pg_extension WHERE extname = 'btree_gin'", - ), - ).resolves.toEqual([{ extname: "btree_gin" }]); - - await expect( - fixture.sql.query<{ is_generated: string }>( - ` -SELECT is_generated -FROM information_schema.columns -WHERE table_name = 'junior_memory_memories' - AND column_name = 'search_vector' -`, - ), - ).resolves.toEqual([{ is_generated: "ALWAYS" }]); - - const [searchIndex] = await fixture.sql.query<{ indexdef: string }>(` -SELECT indexdef -FROM pg_indexes -WHERE indexname = 'junior_memory_memories_search_idx' -`); - expect(searchIndex?.indexdef).toContain( - "USING gin (scope, scope_key, search_vector)", - ); - } finally { - await fixture.close(); - } - }, 15_000); - - it("reports core and nonempty plugin migration journals", async () => { - const fixture = await createEmptyJuniorSqlFixture(); - NEON.sql = fixture.sql; - - try { - const coreMigrationCount = readMigrationFiles({ - migrationsFolder: path.resolve(process.cwd(), "migrations"), - }).length; - const memoryMigrationCount = readMigrationFiles({ - migrationsFolder: memoryMigrationsDir(), - }).length; - const totalMigrationCount = coreMigrationCount + memoryMigrationCount; - const lines: string[] = []; - const pluginSet = defineJuniorPlugins([ - memoryPlugin(), - defineJuniorPlugin({ - manifest: { - description: "Plugin without SQL migrations", - displayName: "Empty", - name: "empty", - }, - }), - ]); - - await runUpgrade({ info: (line) => lines.push(line) }, { pluginSet }); - expect(lines).toEqual([ - "Checking database migrations...", - ` junior: applied ${coreMigrationCount} migrations (${coreMigrationCount} total)`, - ` junior-memory: applied ${memoryMigrationCount} migrations (${memoryMigrationCount} total)`, - `Applied ${totalMigrationCount} migrations (${totalMigrationCount} total).`, - ]); - - lines.length = 0; - await runUpgrade({ info: (line) => lines.push(line) }, { pluginSet }); - expect(lines).toEqual([ - "Checking database migrations...", - ` junior: up to date (${coreMigrationCount} migrations)`, - ` junior-memory: up to date (${memoryMigrationCount} migrations)`, - `Database is up to date (${totalMigrationCount} migrations).`, - ]); - } finally { - await fixture.close(); - } - }, 15_000); - it("reads public memory everywhere and private memory only for its User", async () => { const fixture = await createEmptyJuniorSqlFixture(); - const plugin = memoryPlugin(); - setPlugins([plugin]); + setCoreFeatures([createMemoryFeature()]); NEON.sql = fixture.sql; try { await migrateSchema(fixture.sql); - await migrateMemorySchema(fixture); - // @ts-expect-error non-overlapping boundary cast; rule forbids as-unknown-as chains - const db = fixture.sql.db() as MemoryDb; + const db: MemoryDb = fixture.sql.db(); const viewerConversationId = "slack:D123:1718800001.000000"; const viewer = await recordPrivateConversation(fixture, { channelId: "D123", @@ -483,14 +282,13 @@ WHERE indexname = 'junior_memory_memories_search_idx' } }, 15_000); - it("registers memory tools with runtime-provided plugin DB access", async () => { + it("registers memory tools with runtime-provided core DB access", async () => { const fixture = await createEmptyJuniorSqlFixture(); - setPlugins([memoryPlugin()]); + setCoreFeatures([createMemoryFeature()]); NEON.sql = fixture.sql; try { await migrateSchema(fixture.sql); - await migrateMemorySchema(fixture); const conversationId = "slack:C123:1718800000.000000"; const actor = { platform: "slack" as const, @@ -502,7 +300,6 @@ WHERE indexname = 'junior_memory_memories_search_idx' channelId: "C123", messageTs: "1718800000.000000", threadTs: "1718800000.000000", - visibility: "private", }); const userContext = await recordPrivateConversation(fixture, { @@ -512,17 +309,13 @@ WHERE indexname = 'junior_memory_memories_search_idx' nowMs: Date.parse("2026-08-21T12:00:00.000Z"), userId: actor.userId, }); - const store = createMemoryStore( - // @ts-expect-error non-overlapping boundary cast; rule forbids as-unknown-as chains - fixture.sql.db() as MemoryDb, - { - conversationId, - locationId: userContext.locationId, - actor, - source, - userId: userContext.user.id, - }, - ); + const store = createMemoryStore(fixture.sql.db(), { + conversationId, + locationId: userContext.locationId, + actor, + source, + userId: userContext.user.id, + }); await store.createMemory({ content: "I prefer host-wired personal recall.", idempotencyKey: "component-memory-personal", @@ -551,7 +344,7 @@ WHERE indexname = 'junior_memory_memories_search_idx' resolveActorIdentity: () => readActorIdentity(actor), workspace: {} as Parameters[0]["workspace"], source, - userText: "remember memory plugin facts", + userText: "remember memory facts", }); expect(tools).toHaveProperty("memory_createMemory"); diff --git a/packages/junior-memory/tests/operational-report.test.ts b/packages/junior/tests/component/memory/operational-report.test.ts similarity index 85% rename from packages/junior-memory/tests/operational-report.test.ts rename to packages/junior/tests/component/memory/operational-report.test.ts index 52ffac6e8b..23f751d884 100644 --- a/packages/junior-memory/tests/operational-report.test.ts +++ b/packages/junior/tests/component/memory/operational-report.test.ts @@ -1,21 +1,14 @@ -import { readdir, readFile } from "node:fs/promises"; -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; -import { - createLocalPgliteFixture, - pgliteBtreeGinExtension, - pgliteVectorExtension, - type LocalPgliteFixture, -} from "@sentry/junior-testing/pglite"; import { createLocalSource } from "@sentry/junior-plugin-api"; import { describe, expect, it } from "vitest"; -import * as memorySqlSchema from "../src/db/schema"; -import { juniorMemoryMemories } from "../src/db/schema"; -import { buildMemoryOperationalReport } from "../src/operational-report"; -import { createMemoryStore, type MemoryDb } from "../src/store"; +import { juniorMemoryMemories } from "@/db/schema"; +import { buildMemoryOperationalReport } from "@/chat/memory/operational-report"; +import { createMemoryStore } from "@/chat/memory/store"; +import { + createMemorySqlFixture, + type MemorySqlFixture, +} from "../../fixtures/memory-sql"; const TEST_NOW_MS = Date.parse("2026-07-28T12:00:00.000Z"); -const __dirname = dirname(fileURLToPath(import.meta.url)); function emptyExtractionDays() { const start = Date.parse("2026-04-30T00:00:00.000Z"); @@ -28,24 +21,10 @@ function emptyExtractionDays() { })); } -type MemoryFixture = LocalPgliteFixture; +type MemoryFixture = MemorySqlFixture; async function createMemoryFixture(): Promise { - const fixture = await createLocalPgliteFixture(memorySqlSchema, { - extensions: { - btree_gin: pgliteBtreeGinExtension, - vector: pgliteVectorExtension, - }, - }); - const migrations = (await readdir(resolve(__dirname, "../migrations"))) - .filter((filename) => filename.endsWith(".sql")) - .sort(); - for (const migration of migrations) { - await fixture.execute( - await readFile(resolve(__dirname, "../migrations", migration), "utf8"), - ); - } - return fixture; + return await createMemorySqlFixture(); } function localContext() { diff --git a/packages/junior-memory/tests/retrieval-quality.test.ts b/packages/junior/tests/component/memory/retrieval-quality.test.ts similarity index 80% rename from packages/junior-memory/tests/retrieval-quality.test.ts rename to packages/junior/tests/component/memory/retrieval-quality.test.ts index d48db07675..a89c5a7c77 100644 --- a/packages/junior-memory/tests/retrieval-quality.test.ts +++ b/packages/junior/tests/component/memory/retrieval-quality.test.ts @@ -1,26 +1,17 @@ -import { readdir, readFile } from "node:fs/promises"; -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; -import { - createLocalPgliteFixture, - pgliteBtreeGinExtension, - pgliteVectorExtension, - type LocalPgliteFixture, -} from "@sentry/junior-testing/pglite"; import { createSlackSource } from "@sentry/junior-plugin-api"; import { describe, expect, it } from "vitest"; -import * as memorySqlSchema from "../src/db/schema"; import { createMemoryStore, - type MemoryDb, type MemoryEmbeddingProvider, -} from "../src/store"; +} from "@/chat/memory/store"; +import { + createMemorySqlFixture, + type MemorySqlFixture, +} from "../../fixtures/memory-sql"; const EMBEDDING_DIMENSIONS = 1536; const NOW_MS = Date.parse("2026-07-29T12:00:00.000Z"); -const __dirname = dirname(fileURLToPath(import.meta.url)); - -type MemoryFixture = LocalPgliteFixture; +type MemoryFixture = MemorySqlFixture; function unitEmbedding(index: number): number[] { const embedding = Array.from({ length: EMBEDDING_DIMENSIONS }, () => 0); @@ -44,22 +35,7 @@ function testEmbedder( } async function createFixture(): Promise { - const fixture = await createLocalPgliteFixture(memorySqlSchema, { - extensions: { - btree_gin: pgliteBtreeGinExtension, - vector: pgliteVectorExtension, - }, - }); - const migrationsDir = resolve(__dirname, "../migrations"); - const migrations = (await readdir(migrationsDir)) - .filter((filename) => filename.endsWith(".sql")) - .sort(); - for (const filename of migrations) { - await fixture.execute( - await readFile(resolve(migrationsDir, filename), "utf8"), - ); - } - return fixture; + return await createMemorySqlFixture(); } function runtimeContext() { diff --git a/packages/junior-memory/tests/storage.test.ts b/packages/junior/tests/component/memory/storage.test.ts similarity index 97% rename from packages/junior-memory/tests/storage.test.ts rename to packages/junior/tests/component/memory/storage.test.ts index 015195adcc..d09d8e9999 100644 --- a/packages/junior-memory/tests/storage.test.ts +++ b/packages/junior/tests/component/memory/storage.test.ts @@ -1,12 +1,3 @@ -import { readdir, readFile } from "node:fs/promises"; -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; -import { - createLocalPgliteFixture, - pgliteBtreeGinExtension, - pgliteVectorExtension, - type LocalPgliteFixture, -} from "@sentry/junior-testing/pglite"; import { strictProviderSchemaProblems } from "@sentry/junior-testing/structured-output"; import { createEventSource, @@ -26,36 +17,41 @@ import { import { Command, CommanderError } from "commander"; import { eq } from "drizzle-orm"; import { describe, expect, it, vi } from "vitest"; -import * as memorySqlSchema from "../src/db/schema"; +import * as memorySqlSchema from "@/db/schema/memory"; import { conversationMemoryListResponseSchema, createMemoryApi, memoryApiSchema, memoryDashboardResponseSchema, memoryListResponseSchema, -} from "../src/api"; -import { createMemoryAgent, type CreateMemoryRequest } from "../src/agent"; -import { createMemoryCliCommand } from "../src/cli"; -import { memoryPlugin } from "../src/plugin"; -import { processMemorySession } from "../src/process-session"; +} from "@/chat/memory/api"; +import { + createMemoryAgent, + type CreateMemoryRequest, +} from "@/chat/memory/agent"; +import { createMemoryCliCommand } from "@/chat/memory/cli"; +import { createMemoryFeature } from "@/chat/memory/feature"; +import { processMemorySession } from "@/chat/memory/process-session"; import { createMemoryArchiveTool, createMemoryCreateTool, createMemoryListTool, createMemorySearchTool, type MemoryReviewer, -} from "../src/tools"; -import { listMemories } from "../src/viewer"; -import { createMemoryStore, type MemoryDb } from "../src/store"; +} from "@/chat/memory/tools"; +import { listMemories } from "@/chat/memory/viewer"; +import { createMemoryStore, type MemoryDb } from "@/chat/memory/store"; import type { MemorySupersessionDecider, MemorySupersessionInput, -} from "../src/store"; +} from "@/chat/memory/store"; +import { + createMemorySqlFixture, + type MemorySqlFixture, +} from "../../fixtures/memory-sql"; const TEST_NOW_MS = Date.parse("2026-06-19T12:00:00.000Z"); const TEST_EMBEDDING_DIMENSIONS = 1536; -const __dirname = dirname(fileURLToPath(import.meta.url)); - -type MemoryFixture = LocalPgliteFixture; +type MemoryFixture = MemorySqlFixture; const noopLogger: PluginLogger = { error() {}, @@ -124,8 +120,12 @@ async function runMemoryCli(fixture: MemoryFixture, argv: string[]) { stdout.push(text); }, }, - writeError: (text: string) => stderr.push(text), - writeOutput: (text: string) => stdout.push(text), + writeError: (text: string) => { + stderr.push(text); + }, + writeOutput: (text: string) => { + stdout.push(text); + }, }; const command = new Command(definition.name) .description(definition.summary) @@ -182,43 +182,7 @@ async function runMemoryCli(fixture: MemoryFixture, argv: string[]) { } async function createMemoryFixture(): Promise { - const fixture = await createLocalPgliteFixture(memorySqlSchema, { - extensions: { - btree_gin: pgliteBtreeGinExtension, - vector: pgliteVectorExtension, - }, - }); - const migrationsDir = resolve(__dirname, "../migrations"); - const migrations = (await readdir(migrationsDir)) - .filter((filename) => filename.endsWith(".sql")) - .sort(); - for (const migrationFile of migrations) { - const migration = await readFile(resolve(migrationsDir, migrationFile), { - encoding: "utf8", - }); - await fixture.execute(migration); - } - return fixture; -} - -async function installViewerCoreTables(fixture: MemoryFixture): Promise { - await fixture.execute(` -CREATE TABLE junior_destinations ( - id TEXT PRIMARY KEY, - provider TEXT NOT NULL, - provider_tenant_id TEXT NOT NULL, - provider_destination_id TEXT NOT NULL -); -CREATE TABLE junior_conversations ( - conversation_id TEXT PRIMARY KEY, - root_conversation_id TEXT, - destination_id TEXT -); -CREATE TABLE junior_conversation_participants ( - user_id TEXT NOT NULL, - root_conversation_id TEXT NOT NULL -) -`); + return await createMemorySqlFixture(); } function unitEmbedding(index: number): number[] { @@ -266,6 +230,7 @@ function recallModel( costUsd?: number, ): PluginModel { return { + // @ts-expect-error test model returns one fixed object shape async completeObject(input) { const encoded = /\n(.+)\n<\/candidate-memories>/s.exec( @@ -306,6 +271,7 @@ function extractionModel( ) { const calls: Parameters[0][] = []; const model: PluginModel = { + // @ts-expect-error test model returns one fixed object shape async completeObject(input) { calls.push(input); const toResponseMemory = (memory: (typeof memories)[number]) => ({ @@ -573,10 +539,11 @@ const rejectMemory: MemoryReviewer = { }, }; -describe("memory plugin storage", () => { +describe("memory storage", () => { it("normalizes structured review responses", async () => { const calls: Parameters[0][] = []; const model: PluginModel = { + // @ts-expect-error test model returns one fixed object shape async completeObject(input) { calls.push(input); return { @@ -608,6 +575,7 @@ describe("memory plugin storage", () => { it("normalizes recall selection to known unique candidate ids", async () => { const calls: Parameters[0][] = []; const model: PluginModel = { + // @ts-expect-error test model returns one fixed object shape async completeObject(input) { calls.push(input); return { @@ -642,8 +610,8 @@ describe("memory plugin storage", () => { expect(strictProviderSchemaProblems(calls[0]!.schema)).toEqual([]); }); - it("registers explicit model id as memory plugin model configuration", () => { - const plugin = memoryPlugin({ + it("registers explicit model id as memory model configuration", () => { + const plugin = createMemoryFeature({ modelId: "anthropic/claude-sonnet-4.6", }); @@ -657,7 +625,7 @@ describe("memory plugin storage", () => { delete process.env.AI_MEMORY_MODEL; try { - const plugin = memoryPlugin(); + const plugin = createMemoryFeature(); expect(plugin.model).toEqual({ structuredModel: "default", }); @@ -671,19 +639,19 @@ describe("memory plugin storage", () => { }); it("configures automatic recall and passive extraction independently", () => { - const defaults = memoryPlugin(); + const defaults = createMemoryFeature(); expect(defaults.hooks?.userPrompt).toBeTypeOf("function"); expect(defaults.tasks?.processSession).toBeDefined(); - const withoutRecall = memoryPlugin({ disableRecall: true }); + const withoutRecall = createMemoryFeature({ disableRecall: true }); expect(withoutRecall.hooks?.userPrompt).toBeUndefined(); expect(withoutRecall.tasks?.processSession).toBeDefined(); - const withoutExtraction = memoryPlugin({ disableExtraction: true }); + const withoutExtraction = createMemoryFeature({ disableExtraction: true }); expect(withoutExtraction.hooks?.userPrompt).toBeTypeOf("function"); expect(withoutExtraction.tasks?.processSession).toBeUndefined(); - const withoutEither = memoryPlugin({ + const withoutEither = createMemoryFeature({ disableExtraction: true, disableRecall: true, }); @@ -693,6 +661,7 @@ describe("memory plugin storage", () => { it("parses canonical actor extraction into stored memory text", async () => { const model: PluginModel = { + // @ts-expect-error test model returns one fixed object shape async completeObject() { return { object: { @@ -713,6 +682,7 @@ describe("memory plugin storage", () => { await expect( agent.extractSessionMemories({ + existingMemories: [], transcript: [ { type: "message", @@ -742,6 +712,7 @@ describe("memory plugin storage", () => { it("accepts up to five passive extraction memories", async () => { const model: PluginModel = { + // @ts-expect-error test model returns one fixed object shape async completeObject() { return { object: { @@ -784,6 +755,7 @@ describe("memory plugin storage", () => { const agent = createMemoryAgent(model); const result = await agent.extractSessionMemories({ + existingMemories: [], transcript: [ { type: "message", @@ -800,6 +772,7 @@ describe("memory plugin storage", () => { it("rejects passive extraction responses with more than five memories", async () => { const model: PluginModel = { + // @ts-expect-error test model returns one fixed object shape async completeObject() { return { object: { @@ -817,6 +790,7 @@ describe("memory plugin storage", () => { await expect( agent.extractSessionMemories({ + existingMemories: [], transcript: [ { type: "message", @@ -830,12 +804,12 @@ describe("memory plugin storage", () => { ).rejects.toThrow("Too big"); }); - it("uses AI_MEMORY_MODEL as the memory plugin model default", async () => { + it("uses AI_MEMORY_MODEL as the memory model default", async () => { const previousModel = process.env.AI_MEMORY_MODEL; process.env.AI_MEMORY_MODEL = "anthropic/claude-sonnet-4.6"; try { - const plugin = memoryPlugin(); + const plugin = createMemoryFeature(); expect(plugin.model).toEqual({ structuredModelId: "anthropic/claude-sonnet-4.6", }); @@ -850,6 +824,7 @@ describe("memory plugin storage", () => { it("normalizes structured rejection responses", async () => { const model: PluginModel = { + // @ts-expect-error test model returns one fixed object shape async completeObject() { return { object: { @@ -1056,6 +1031,7 @@ describe("memory plugin storage", () => { const supersessionCalls: Parameters[0][] = []; const model: PluginModel = { + // @ts-expect-error test model returns one fixed object shape async completeObject(input) { if ( typeof input.prompt === "string" && @@ -1144,6 +1120,7 @@ describe("memory plugin storage", () => { try { const model: PluginModel = { + // @ts-expect-error test model returns one fixed object shape async completeObject(input) { if ( typeof input.prompt !== "string" || @@ -1556,6 +1533,7 @@ describe("memory plugin storage", () => { const emitted: Parameters[0][] = []; let failFirstEmit = true; const model: PluginModel = { + // @ts-expect-error test model returns one fixed object shape async completeObject(input) { if ( typeof input.prompt === "string" && @@ -1807,7 +1785,6 @@ describe("memory plugin storage", () => { }); try { - await installViewerCoreTables(fixture); const store = createMemoryStore(memoryDb(fixture), runtime, { now: () => TEST_NOW_MS, }); @@ -2480,7 +2457,6 @@ describe("memory plugin storage", () => { const now = vi.spyOn(Date, "now").mockReturnValue(TEST_NOW_MS); try { - await installViewerCoreTables(fixture); const firstContext = slackContext({ teamId: "T123", userId: "U123" }); const secondContext = slackContext({ teamId: "T456", userId: "U456" }); const hiddenContext = slackContext({ @@ -3690,7 +3666,7 @@ WHERE id = '${superseded.memory.id}' expect(tools.createMemory.approvalMode).toBe("approve"); await expect( - tools.createMemory.execute( + tools.createMemory.execute!( { content: "I prefer terse status updates.", }, @@ -3742,7 +3718,7 @@ WHERE id = '${superseded.memory.id}' createMemoryCreateTool({ ...context, agent: allowMemory("conversation"), - }).execute( + }).execute!( { content: "Incident notes live in Linear.", expires_at: "never", @@ -3773,7 +3749,7 @@ WHERE id = '${superseded.memory.id}' ]); await expect( - tools.listMemories.execute({ limit: 10 }, {}), + tools.listMemories.execute!({ limit: 10 }, {}), ).resolves.toMatchObject({ memories: [ expect.objectContaining({ @@ -3785,7 +3761,7 @@ WHERE id = '${superseded.memory.id}' ], }); await expect( - tools.searchMemories.execute({ query: "incident notes" }, {}), + tools.searchMemories.execute!({ query: "incident notes" }, {}), ).resolves.toMatchObject({ memories: [ expect.objectContaining({ @@ -3794,7 +3770,7 @@ WHERE id = '${superseded.memory.id}' ], }); - const listResult = (await tools.listMemories.execute( + const listResult = (await tools.listMemories.execute!( { limit: 10 }, {}, )) as { @@ -3805,7 +3781,7 @@ WHERE id = '${superseded.memory.id}' ); expect(personal).toBeDefined(); await expect( - tools.archiveMemory.execute({ id: personal!.id.slice(0, 12) }, {}), + tools.archiveMemory.execute!({ id: personal!.id.slice(0, 12) }, {}), ).resolves.toMatchObject({ memory: { id: personal!.id, @@ -3813,7 +3789,7 @@ WHERE id = '${superseded.memory.id}' }, }); await expect( - tools.searchMemories.execute({ query: "terse status" }, {}), + tools.searchMemories.execute!({ query: "terse status" }, {}), ).resolves.toMatchObject({ memories: [] }); await expect( @@ -3826,7 +3802,7 @@ WHERE id = '${superseded.memory.id}' ); }, }, - }).execute( + }).execute!( { content: "I prefer missing retry ids to fail.", expires_at: "never", @@ -3835,7 +3811,7 @@ WHERE id = '${superseded.memory.id}' ), ).rejects.toThrow(PluginToolInputError); await expect( - tools.createMemory.execute( + tools.createMemory.execute!( { content: "I prefer invalid expiration to fail.", expires_at: "not-a-date", @@ -3844,7 +3820,7 @@ WHERE id = '${superseded.memory.id}' ), ).rejects.toThrow(PluginToolInputError); await expect( - tools.createMemory.execute( + tools.createMemory.execute!( { content: "I prefer valid expiration to be stored.", expires_at: "2026-06-19T13:00:00+00:00", @@ -3859,7 +3835,7 @@ WHERE id = '${superseded.memory.id}' }, }); await expect( - tools.createMemory.execute( + tools.createMemory.execute!( { content: "I prefer hidden fields to fail.", expires_at: "never", @@ -3869,7 +3845,7 @@ WHERE id = '${superseded.memory.id}' ), ).rejects.toThrow(PluginToolInputError); await expect( - tools.createMemory.execute( + tools.createMemory.execute!( { content: " \n\t ", expires_at: "never", @@ -3883,7 +3859,7 @@ WHERE id = '${superseded.memory.id}' db: memoryDb(fixture), ...slackContext(), users: memoryUsers(slackContext().userId), - }).execute( + }).execute!( { content: "I prefer rejected memories not to be stored.", expires_at: "never", @@ -3901,7 +3877,7 @@ WHERE id = '${superseded.memory.id}' return undefined; }, }, - }).execute( + }).execute!( { content: "I prefer actor context failures to be visible.", expires_at: "never", @@ -3910,7 +3886,7 @@ WHERE id = '${superseded.memory.id}' ), ).rejects.toThrow(PluginToolInputError); await expect( - tools.createMemory.execute( + tools.createMemory.execute!( { content: "I prefer duplicate-safe retries.", expires_at: "never", @@ -3922,7 +3898,7 @@ WHERE id = '${superseded.memory.id}' memory: { content: "Prefers duplicate-safe retries." }, }); await expect( - tools.searchMemories.execute({ query: "duplicate-safe retries" }, {}), + tools.searchMemories.execute!({ query: "duplicate-safe retries" }, {}), ).resolves.toMatchObject({ memories: [ expect.objectContaining({ @@ -3974,7 +3950,7 @@ WHERE id = '${superseded.memory.id}' }); const emitted: PluginConversationEventValue[] = []; - const plugin = memoryPlugin(); + const plugin = createMemoryFeature(); const result = await plugin.hooks?.userPrompt?.({ ...context, destination: slackDestination(context), @@ -4066,7 +4042,7 @@ WHERE id = '${superseded.memory.id}' } const emitted: PluginConversationEventValue[] = []; - const plugin = memoryPlugin(); + const plugin = createMemoryFeature(); const result = await plugin.hooks?.userPrompt?.({ ...context, destination: slackDestination(context), @@ -4090,9 +4066,9 @@ WHERE id = '${superseded.memory.id}' if (!contribution || !("context" in contribution)) { throw new Error("Memory recall did not return structured context"); } - const admittedIds = contribution.context.content.memories.map( - (memory) => memory.id, - ); + const admittedIds = ( + contribution.context.content.memories as Array<{ id: string }> + ).map((memory) => memory.id); expect(admittedIds).toHaveLength(6); expect([...admittedIds].sort()).toEqual( created.map(({ memory }) => memory.id).sort(), @@ -4124,7 +4100,7 @@ WHERE id = '${superseded.memory.id}' idempotencyKey: "memory-test:recall-conversation-context", }); - const plugin = memoryPlugin(); + const plugin = createMemoryFeature(); const result = await plugin.hooks?.userPrompt?.({ ...context, destination: slackDestination(context), @@ -4196,7 +4172,7 @@ WHERE id = '${superseded.memory.id}' idempotencyKey: "memory-test:recall-gate-unrelated", }); - const plugin = memoryPlugin(); + const plugin = createMemoryFeature(); const result = await plugin.hooks?.userPrompt?.({ ...context, destination: slackDestination(context), @@ -4241,7 +4217,7 @@ WHERE id = '${superseded.memory.id}' }); const emitted: PluginConversationEventValue[] = []; - const plugin = memoryPlugin(); + const plugin = createMemoryFeature(); await expect( plugin.hooks?.userPrompt?.({ ...context, @@ -4281,7 +4257,7 @@ WHERE id = '${superseded.memory.id}' try { const context = slackContext(); - const plugin = memoryPlugin(); + const plugin = createMemoryFeature(); await expect( plugin.hooks?.userPrompt?.({ ...context, @@ -4340,7 +4316,7 @@ WHERE id = '${superseded.memory.id}' idempotencyKey: "memory-test:recall-gate-failure", }); - const plugin = memoryPlugin(); + const plugin = createMemoryFeature(); await expect( plugin.hooks?.userPrompt?.({ ...context, @@ -4376,7 +4352,7 @@ WHERE id = '${superseded.memory.id}' idempotencyKey: "memory-test:recall-blank", }); - const plugin = memoryPlugin(); + const plugin = createMemoryFeature(); await expect( plugin.hooks?.userPrompt?.({ ...context, @@ -4416,7 +4392,7 @@ WHERE id = '${superseded.memory.id}' idempotencyKey: "memory-test:recall-semantic", }); - const plugin = memoryPlugin(); + const plugin = createMemoryFeature(); const result = await plugin.hooks?.userPrompt?.({ ...context, destination: slackDestination(context), @@ -4460,7 +4436,7 @@ WHERE id = '${superseded.memory.id}' }); await expect( - firstTool.execute( + firstTool.execute!( { content: "I prefer the first remembered fact.", expires_at: "never", @@ -4469,7 +4445,7 @@ WHERE id = '${superseded.memory.id}' ), ).resolves.toMatchObject({ created: true }); await expect( - secondTool.execute( + secondTool.execute!( { content: "I prefer the second remembered fact.", expires_at: "never", @@ -4625,6 +4601,7 @@ INSERT INTO junior_memory_memories ( try { let duplicateId: string | undefined; const model: PluginModel = { + // @ts-expect-error test model returns one fixed object shape async completeObject(input) { expect(input.prompt).toContain( "", diff --git a/packages/junior/tests/component/plugins/plugin-conversation-events.test.ts b/packages/junior/tests/component/plugins/plugin-conversation-events.test.ts index 96f42da821..ef0e75c102 100644 --- a/packages/junior/tests/component/plugins/plugin-conversation-events.test.ts +++ b/packages/junior/tests/component/plugins/plugin-conversation-events.test.ts @@ -83,8 +83,10 @@ beforeEach(() => { afterEach(async () => { const { setPlugins } = await import("@/chat/plugins/agent-hooks"); + const { setCoreFeatures } = await import("@/chat/plugins/core-features"); const { disconnectStateAdapter } = await import("@/chat/state/adapter"); setPlugins([]); + setCoreFeatures([]); await disconnectStateAdapter(); vi.resetModules(); process.env = { ...ORIGINAL_ENV }; @@ -92,8 +94,12 @@ afterEach(async () => { describe("plugin conversation events", () => { it("renders core Brief events outside the installed plugin catalog", async () => { + const { briefsFeature } = await import("@/chat/briefs/task"); + const { setCoreFeatures } = await import("@/chat/plugins/core-features"); const { renderPluginConversationEvent } = await import("@/chat/plugins/conversation-events"); + // Disabled core features keep their event definitions for stored events. + setCoreFeatures([briefsFeature({ enabled: false })]); expect( renderPluginConversationEvent({ diff --git a/packages/junior/tests/component/plugins/plugin-migrations.test.ts b/packages/junior/tests/component/plugins/plugin-migrations.test.ts new file mode 100644 index 0000000000..b8ee75829b --- /dev/null +++ b/packages/junior/tests/component/plugins/plugin-migrations.test.ts @@ -0,0 +1,113 @@ +import path from "node:path"; +import { readdirSync } from "node:fs"; +import { readMigrationFiles } from "drizzle-orm/migrator"; +import { describe, expect, it } from "vitest"; +import { migratePluginSchemas } from "@/chat/plugins/migrations"; +import { createEmptyJuniorSqlFixture } from "../../fixtures/sql"; + +const PLUGIN_NAME = "test-plugin"; + +function pluginMigrationsDir(): string { + return path.resolve(process.cwd(), "tests/fixtures/plugin-migrations"); +} + +function pluginMigrationFiles(): string[] { + return readdirSync(pluginMigrationsDir()) + .filter((filename) => filename.endsWith(".sql")) + .sort(); +} + +async function migratePlugin( + fixture: Awaited>, +) { + return await migratePluginSchemas(fixture.sql, [ + { dir: pluginMigrationsDir(), pluginName: PLUGIN_NAME }, + ]); +} + +describe("plugin SQL migrations", () => { + it("adopts exact legacy migration hashes without replaying them", async () => { + const fixture = await createEmptyJuniorSqlFixture(); + const migrations = readMigrationFiles({ + migrationsFolder: pluginMigrationsDir(), + }); + const migrationFiles = pluginMigrationFiles(); + expect(migrationFiles).toHaveLength(migrations.length); + + try { + await migratePlugin(fixture); + const [migrationTable] = await fixture.sql.query<{ tablename: string }>(` +SELECT tablename +FROM pg_tables +WHERE schemaname = 'drizzle' + AND tablename LIKE '__drizzle_test_plugin_%' +`); + expect(migrationTable).toBeDefined(); + await fixture.sql.execute( + `DROP TABLE drizzle.${migrationTable!.tablename}`, + ); + await fixture.sql.execute(` +CREATE TABLE junior_schema_migrations ( + id TEXT PRIMARY KEY, + checksum TEXT NOT NULL, + applied_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP +) +`); + for (const [index, migration] of migrations.entries()) { + await fixture.sql.execute( + `INSERT INTO junior_schema_migrations (id, checksum) VALUES ($1, $2)`, + [`plugin:${PLUGIN_NAME}/${migrationFiles[index]}`, migration.hash], + ); + } + + await expect(migratePlugin(fixture)).resolves.toEqual({ + existing: migrations.length, + migrated: 0, + scanned: migrations.length, + }); + } finally { + await fixture.close(); + } + }, 15_000); + + it("does not adopt an unknown legacy checksum", async () => { + const fixture = await createEmptyJuniorSqlFixture(); + const migrationCount = readMigrationFiles({ + migrationsFolder: pluginMigrationsDir(), + }).length; + const [baselineFile] = pluginMigrationFiles(); + expect(baselineFile).toBeDefined(); + + try { + await fixture.sql.execute(` +CREATE TABLE junior_schema_migrations ( + id TEXT PRIMARY KEY, + checksum TEXT NOT NULL, + applied_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP +) +`); + await fixture.sql.execute( + `INSERT INTO junior_schema_migrations (id, checksum) VALUES ($1, $2)`, + [`plugin:${PLUGIN_NAME}/${baselineFile}`, "unknown-checksum"], + ); + + await expect(migratePlugin(fixture)).resolves.toEqual({ + existing: 0, + migrated: migrationCount, + scanned: migrationCount, + }); + await expect( + fixture.sql.query<{ column_name: string }>( + ` +SELECT column_name +FROM information_schema.columns +WHERE table_name = 'test_plugin_items' +ORDER BY column_name +`, + ), + ).resolves.toEqual([{ column_name: "id" }, { column_name: "note" }]); + } finally { + await fixture.close(); + } + }, 15_000); +}); diff --git a/packages/junior/tests/component/plugins/plugin-prompt-hooks.test.ts b/packages/junior/tests/component/plugins/plugin-prompt-hooks.test.ts index 0656f28f18..b6905f1075 100644 --- a/packages/junior/tests/component/plugins/plugin-prompt-hooks.test.ts +++ b/packages/junior/tests/component/plugins/plugin-prompt-hooks.test.ts @@ -141,8 +141,8 @@ describe("plugin prompt hook composition", () => { previousPlugins = setPlugins([ defineJuniorPlugin({ manifest: { - name: "memory", - displayName: "Memory", + name: "recall", + displayName: "Recall", description: "Memory test plugin", }, hooks: { @@ -209,8 +209,8 @@ describe("plugin prompt hook composition", () => { setPlugins([ defineJuniorPlugin({ manifest: { - name: "memory", - displayName: "Memory", + name: "recall", + displayName: "Recall", description: "Memory test plugin", }, hooks: { @@ -239,12 +239,12 @@ describe("plugin prompt hook composition", () => { ); const stored = await getConversationEventStore().loadByIdempotencyKey( LOCAL_DESTINATION.conversationId, - `turn:${turnId}:context:memory:0`, + `turn:${turnId}:context:recall:0`, ); expect(stored?.data).toEqual({ type: "turn_context", turnId, - pluginName: "memory", + pluginName: "recall", kind: "recall", version: 1, content: { @@ -266,8 +266,8 @@ describe("plugin prompt hook composition", () => { setPlugins([ defineJuniorPlugin({ manifest: { - name: "memory", - displayName: "Memory", + name: "recall", + displayName: "Recall", description: "Memory test plugin", }, hooks: { @@ -318,7 +318,7 @@ describe("plugin prompt hook composition", () => { ); const stored = await getConversationEventStore().loadByIdempotencyKey( LOCAL_DESTINATION.conversationId, - `turn:${turnId}:context:memory:0`, + `turn:${turnId}:context:recall:0`, ); expect(stored?.data).toMatchObject({ type: "turn_context", diff --git a/packages/junior/tests/fixtures/app.ts b/packages/junior/tests/fixtures/app.ts new file mode 100644 index 0000000000..68d6d4d6c1 --- /dev/null +++ b/packages/junior/tests/fixtures/app.ts @@ -0,0 +1,17 @@ +import { createApp, type JuniorAppOptions } from "@/app"; + +/** + * Build the production app for tests that run Turns. + * + * Memory adds recall and extraction model calls to every Turn, so it stays off + * unless a test passes `memory`. Tests about `createApp()` itself call it + * directly. + */ +export async function createTestApp( + options: JuniorAppOptions = {}, +): ReturnType { + return await createApp({ + ...options, + memory: options.memory ?? { enabled: false }, + }); +} diff --git a/packages/junior/tests/fixtures/memory-sql.ts b/packages/junior/tests/fixtures/memory-sql.ts new file mode 100644 index 0000000000..1d68e5f002 --- /dev/null +++ b/packages/junior/tests/fixtures/memory-sql.ts @@ -0,0 +1,20 @@ +import { + createLocalPgliteFixture, + type LocalPgliteFixture, +} from "@sentry/junior-testing/pglite"; +import type { PgliteDatabase } from "drizzle-orm/pglite"; +import { migrateSchema } from "@/chat/conversations/sql/migrations"; +import type { MemoryDb } from "@/chat/memory/store"; +import { juniorSqlSchema } from "@/db/schema"; +import { pgliteJuniorExtensions, pgliteJuniorSqlExecutor } from "./sql"; + +export type MemorySqlFixture = LocalPgliteFixture; + +/** Create an in-memory PGlite database with the migrated core schema. */ +export async function createMemorySqlFixture(): Promise { + const fixture = await createLocalPgliteFixture< + PgliteDatabase + >(juniorSqlSchema, { extensions: pgliteJuniorExtensions }); + await migrateSchema(pgliteJuniorSqlExecutor(fixture)); + return fixture as MemorySqlFixture; +} diff --git a/packages/junior/tests/fixtures/plugin-migrations/0000_test_plugin_items.sql b/packages/junior/tests/fixtures/plugin-migrations/0000_test_plugin_items.sql new file mode 100644 index 0000000000..05ef05e4fe --- /dev/null +++ b/packages/junior/tests/fixtures/plugin-migrations/0000_test_plugin_items.sql @@ -0,0 +1,3 @@ +CREATE TABLE "test_plugin_items" ( + "id" text PRIMARY KEY NOT NULL +); diff --git a/packages/junior/tests/fixtures/plugin-migrations/0001_test_plugin_items_note.sql b/packages/junior/tests/fixtures/plugin-migrations/0001_test_plugin_items_note.sql new file mode 100644 index 0000000000..be56671bac --- /dev/null +++ b/packages/junior/tests/fixtures/plugin-migrations/0001_test_plugin_items_note.sql @@ -0,0 +1 @@ +ALTER TABLE "test_plugin_items" ADD COLUMN "note" text; diff --git a/packages/junior/tests/fixtures/plugin-migrations/meta/_journal.json b/packages/junior/tests/fixtures/plugin-migrations/meta/_journal.json new file mode 100644 index 0000000000..23ee5d6b58 --- /dev/null +++ b/packages/junior/tests/fixtures/plugin-migrations/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1700000000000, + "tag": "0000_test_plugin_items", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1700000001000, + "tag": "0001_test_plugin_items_note", + "breakpoints": true + } + ] +} diff --git a/packages/junior/tests/fixtures/sql.ts b/packages/junior/tests/fixtures/sql.ts index 6070ef7833..799b193c3e 100644 --- a/packages/junior/tests/fixtures/sql.ts +++ b/packages/junior/tests/fixtures/sql.ts @@ -3,6 +3,9 @@ import type { JuniorDatabase, JuniorSqlExecutor } from "@/db/db"; import { juniorSqlSchema } from "@/db/schema"; import { createLocalPgliteFixture, + pgliteBtreeGinExtension, + pglitePgcryptoExtension, + pgliteVectorExtension, type LocalPgliteFixture, } from "@sentry/junior-testing/pglite"; import { migrate } from "drizzle-orm/pglite/migrator"; @@ -29,12 +32,18 @@ export function hasJuniorPostgresTestDatabase(): boolean { return hasJuniorPostgresTestDatabaseFixture(); } -async function createPgliteJuniorSqlFixture(): Promise { - const fixture = - await createLocalPgliteFixture>( - juniorSqlSchema, - ); - const sql: JuniorSqlExecutor = { +/** Extensions the core schema needs; PGlite loads them at startup. */ +export const pgliteJuniorExtensions = { + btree_gin: pgliteBtreeGinExtension, + pgcrypto: pglitePgcryptoExtension, + vector: pgliteVectorExtension, +}; + +/** Adapt a PGlite fixture to the Junior SQL executor contract. */ +export function pgliteJuniorSqlExecutor( + fixture: LocalPgliteFixture>, +): JuniorSqlExecutor { + return { close: () => fixture.close(), db: () => fixture.db() as JuniorDatabase, execute: (statement, params) => fixture.execute(statement, params), @@ -45,13 +54,29 @@ async function createPgliteJuniorSqlFixture(): Promise { withLock: (lockName, callback) => fixture.withLock(lockName, callback), withMigrationLock: (_migrationTable, callback) => callback(), }; +} + +async function createPgliteJuniorSqlFixture( + options: { extensions: boolean } = { extensions: true }, +): Promise { + const fixture = await createLocalPgliteFixture< + PgliteDatabase + >( + juniorSqlSchema, + options.extensions ? { extensions: pgliteJuniorExtensions } : {}, + ); return { client: fixture.client, - sql, + sql: pgliteJuniorSqlExecutor(fixture), close: () => fixture.close(), }; } +/** Create an empty PGlite database without the extensions the core schema needs. */ +export async function createUnextendedJuniorSqlFixture(): Promise { + return await createPgliteJuniorSqlFixture({ extensions: false }); +} + /** Create an isolated fixture with the current Junior schema. */ export async function createJuniorSqlFixture(): Promise { if (hasJuniorPostgresTestDatabase()) { diff --git a/packages/junior/tests/integration/acp-http-recovery.test.ts b/packages/junior/tests/integration/acp-http-recovery.test.ts index 14020d4ddd..3efa78371f 100644 --- a/packages/junior/tests/integration/acp-http-recovery.test.ts +++ b/packages/junior/tests/integration/acp-http-recovery.test.ts @@ -4,7 +4,7 @@ import * as acp from "@agentclientprotocol/sdk"; import type { StateAdapter } from "chat"; import { completeAcpAuthorization } from "@/api/acp/auth"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { createApp } from "@/app"; +import { createTestApp } from "../fixtures/app"; import { createAcpConversations } from "@/api/acp/conversations"; import { ConversationTurnLifecycleService } from "@/chat/conversations/turn-lifecycle"; import { getConversationEventStore } from "@/chat/db"; @@ -55,7 +55,7 @@ describe("remote ACP recovery", () => { vi.stubEnv("VERCEL_PROJECT_PRODUCTION_URL", "junior.example.com"); vi.stubEnv("VERCEL_URL", "preview.example.com"); const harness = await createConversationWebHarness(); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, dashboard: { authRequired: false, @@ -164,7 +164,7 @@ describe("remote ACP recovery", () => { vi.stubEnv("VERCEL_PROJECT_PRODUCTION_URL", ""); vi.stubEnv("VERCEL_URL", ""); const harness = await createConversationWebHarness(); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, dashboard: { authRequired: false }, }); @@ -229,7 +229,7 @@ describe("remote ACP recovery", () => { it("rejects a second authenticate request without replacing the pending sign-in", async () => { const harness = await createConversationWebHarness(); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); const initialized = await app.fetch(initializeRequest()); @@ -332,10 +332,10 @@ describe("remote ACP recovery", () => { }, ]), ); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); - const secondApp = await createApp({ + const secondApp = await createTestApp({ conversationWork: createIndependentConversationWork(harness), }); const sessionCreated = deferred(); @@ -413,10 +413,10 @@ describe("remote ACP recovery", () => { const harness = await createConversationWebHarness( streamMcpSearch("Auth-paused Turn must not reply."), ); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); - const secondApp = await createApp({ + const secondApp = await createTestApp({ conversationWork: createIndependentConversationWork(harness), }); const sessionCreated = deferred(); @@ -495,10 +495,10 @@ describe("remote ACP recovery", () => { { type: "text", text: "Cancelled resume must not reply." }, ]), ); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); - const secondApp = await createApp({ + const secondApp = await createTestApp({ conversationWork: createIndependentConversationWork(harness), }); const sessionCreated = deferred(); @@ -601,13 +601,13 @@ describe("remote ACP recovery", () => { return typeof value === "function" ? value.bind(target) : value; }, }) as StateAdapter; - const app = await createApp({ + const app = await createTestApp({ conversationWork: { ...harness.conversationWork, state: observedState, }, }); - const secondApp = await createApp({ + const secondApp = await createTestApp({ conversationWork: createIndependentConversationWork(harness), }); const sessionCreated = deferred(); @@ -819,10 +819,10 @@ describe("remote ACP recovery", () => { const harness = await createConversationWebHarness( streamReplies("Recovered queue reply."), ); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); - const secondApp = await createApp({ + const secondApp = await createTestApp({ conversationWork: createIndependentConversationWork(harness), }); const sessionId = await withAcpClient({ @@ -883,7 +883,7 @@ describe("remote ACP recovery", () => { it("rejects a prompt before acceptance when its output stream is full", async () => { const harness = await createConversationWebHarness(); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); const sessionId = await withAcpClient({ diff --git a/packages/junior/tests/integration/acp-http.test.ts b/packages/junior/tests/integration/acp-http.test.ts index b377650f8a..5121a8ea12 100644 --- a/packages/junior/tests/integration/acp-http.test.ts +++ b/packages/junior/tests/integration/acp-http.test.ts @@ -1,7 +1,7 @@ import * as acp from "@agentclientprotocol/sdk"; import type { StateAdapter } from "chat"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { createApp } from "@/app"; +import { createTestApp } from "../fixtures/app"; import { getConversationEventStore } from "@/chat/db"; import { createPersonalToken } from "@/personal-tokens/store"; import { @@ -32,7 +32,7 @@ describe("remote ACP HTTP", () => { it("mounts the endpoint without extra app config", async () => { const harness = await createConversationWebHarness(); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); @@ -46,7 +46,7 @@ describe("remote ACP HTTP", () => { it("stays mounted without dashboard authentication", async () => { const harness = await createConversationWebHarness(); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, dashboard: { disabled: true }, }); @@ -61,7 +61,7 @@ describe("remote ACP HTTP", () => { it("initializes with isolated cookies and rejects a valid personal token", async () => { const harness = await createConversationWebHarness(); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); @@ -110,7 +110,7 @@ describe("remote ACP HTTP", () => { it("validates JSON-RPC envelopes and initialization", async () => { const harness = await createConversationWebHarness(); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); const nullId = await app.fetch(initializeRequest(null)); @@ -172,7 +172,7 @@ describe("remote ACP HTTP", () => { it("requires ACP authentication before session methods", async () => { const harness = await createConversationWebHarness(); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); @@ -211,10 +211,10 @@ describe("remote ACP HTTP", () => { const harness = await createConversationWebHarness( streamReplies("First ACP reply."), ); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); - const secondApp = await createApp({ + const secondApp = await createTestApp({ conversationWork: createIndependentConversationWork(harness), }); const expectedFirstPromptText = @@ -440,7 +440,7 @@ describe("remote ACP HTTP", () => { const harness = await createConversationWebHarness( streamReplies("Typed id reply."), ); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); @@ -530,10 +530,10 @@ describe("remote ACP HTTP", () => { it("coordinates an SSE stream across handoff and request abort", async () => { const harness = await createConversationWebHarness(); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); - const secondApp = await createApp({ + const secondApp = await createTestApp({ conversationWork: createIndependentConversationWork(harness), }); const initialized = await app.fetch(initializeRequest()); @@ -592,7 +592,7 @@ describe("remote ACP HTTP", () => { return typeof value === "function" ? value.bind(target) : value; }, }); - const app = await createApp({ + const app = await createTestApp({ conversationWork: createIndependentConversationWork(harness, state), }); const initialized = await app.fetch(initializeRequest()); @@ -634,7 +634,7 @@ describe("remote ACP HTTP", () => { const harness = await createConversationWebHarness( streamReplies("Completed after disconnect."), ); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); const promptAccepted = deferred(); @@ -766,10 +766,10 @@ describe("remote ACP HTTP", () => { }, ]), ); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); - const secondApp = await createApp({ + const secondApp = await createTestApp({ conversationWork: createIndependentConversationWork(harness), }); const sessionCreated = deferred(); @@ -872,7 +872,7 @@ describe("remote ACP HTTP", () => { const harness = await createConversationWebHarness( createModelStream([{ type: "error", errorMessage: "model unavailable" }]), ); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); let sessionId: string | undefined; @@ -928,7 +928,7 @@ describe("remote ACP HTTP", () => { it("rejects unsupported MCP and prompt content at the protocol boundary", async () => { const harness = await createConversationWebHarness(); - const app = await createApp({ + const app = await createTestApp({ conversationWork: harness.conversationWork, }); const errors = await withAcpClient({ diff --git a/packages/junior/tests/integration/api/memory.test.ts b/packages/junior/tests/integration/api/memory.test.ts new file mode 100644 index 0000000000..dbdd384f20 --- /dev/null +++ b/packages/junior/tests/integration/api/memory.test.ts @@ -0,0 +1,138 @@ +import { Hono } from "hono"; +import { afterEach, describe, expect, test } from "vitest"; +import { createSlackSource } from "@sentry/junior-plugin-api"; +import { createJuniorApi } from "@/api"; +import type { JuniorApiEnv } from "@/api/route"; +import { getDb } from "@/chat/db"; +import { migrateSchema } from "@/chat/conversations/sql/migrations"; +import { createSqlStore } from "@/chat/conversations/sql/store"; +import { memoryApiSchema, memoryListResponseSchema } from "@/chat/memory/api"; +import { createMemoryFeature } from "@/chat/memory/feature"; +import { createMemoryStore } from "@/chat/memory/store"; +import { setCoreFeatures } from "@/chat/plugins/core-features"; +import { resolveViewerUser } from "@/chat/plugins/viewer"; +import { createConfiguredJuniorSqlFixture } from "../../fixtures/sql"; + +const VIEWER_EMAIL = "memory-api@example.com"; + +function authenticatedApi(email: string) { + const app = new Hono(); + app.use("*", async (context, next) => { + const viewer = await resolveViewerUser(email); + if (!viewer) { + throw new Error(`missing viewer for ${email}`); + } + context.set("viewer", viewer); + await next(); + }); + app.route("/", createJuniorApi()); + return app; +} + +async function seedPrivateMemory(fixture: { + sql: Parameters[0]; +}) { + const conversationId = "slack:D123:1718800001.000000"; + const conversations = createSqlStore(fixture.sql); + await conversations.recordActivity({ + actor: { + email: VIEWER_EMAIL, + platform: "slack", + slackUserId: "U123", + teamId: "T123", + }, + conversationId, + destination: { channelId: "D123", platform: "slack", teamId: "T123" }, + nowMs: Date.parse("2026-08-21T12:00:00.000Z"), + source: "slack", + visibility: "private", + }); + const viewer = await resolveViewerUser(VIEWER_EMAIL); + if (!viewer) { + throw new Error("Viewer did not resolve"); + } + const created = await createMemoryStore(getDb(), { + conversationId, + actor: { platform: "slack", teamId: "T123", userId: "U123" }, + source: createSlackSource({ + teamId: "T123", + channelId: "D123", + messageTs: "1718800001.000000", + visibility: "private", + }), + userId: viewer.id, + }).createMemory({ + content: "Prefers terse status updates.", + idempotencyKey: "api-memory-private", + kind: "preference", + }); + return created.memory; +} + +describe("memory API routes", () => { + afterEach(() => setCoreFeatures([])); + + test("lists, reads, and forgets memory through the core routes", async () => { + const fixture = createConfiguredJuniorSqlFixture(); + setCoreFeatures([createMemoryFeature()]); + try { + await migrateSchema(fixture.sql); + const memory = await seedPrivateMemory(fixture); + const api = authenticatedApi(VIEWER_EMAIL); + + const listResponse = await api.request( + "http://localhost/api/memory/memories", + ); + expect(listResponse.status).toBe(200); + expect( + memoryListResponseSchema + .parse(await listResponse.json()) + .memories.map((entry) => entry.id), + ).toEqual([memory.id]); + + const detailResponse = await api.request( + `http://localhost/api/memory/memories/${encodeURIComponent(memory.id)}`, + ); + expect(detailResponse.status).toBe(200); + expect(memoryApiSchema.parse(await detailResponse.json())).toMatchObject({ + id: memory.id, + visibility: "private", + }); + + const deleteResponse = await api.request( + `http://localhost/api/memory/memories/${encodeURIComponent(memory.id)}`, + { method: "DELETE" }, + ); + expect(deleteResponse.status).toBe(204); + const forgottenResponse = await api.request( + `http://localhost/api/memory/memories/${encodeURIComponent(memory.id)}`, + ); + expect(forgottenResponse.status).toBe(404); + } finally { + await fixture.close(); + } + }); + + test("requires an authenticated viewer", async () => { + setCoreFeatures([createMemoryFeature()]); + const response = await createJuniorApi().request( + "http://localhost/api/memory/memories", + ); + expect(response.status).toBe(401); + }); + + test("returns not found when Memory is disabled", async () => { + const fixture = createConfiguredJuniorSqlFixture(); + setCoreFeatures([createMemoryFeature({ enabled: false })]); + try { + await migrateSchema(fixture.sql); + await seedPrivateMemory(fixture); + const response = await authenticatedApi(VIEWER_EMAIL).request( + "http://localhost/api/memory/memories", + ); + expect(response.status).toBe(404); + } finally { + await fixture.close(); + } + }); +}); diff --git a/packages/junior/tests/integration/api/plugin-reports.test.ts b/packages/junior/tests/integration/api/plugin-reports.test.ts index 1facd2bf99..444469cbeb 100644 --- a/packages/junior/tests/integration/api/plugin-reports.test.ts +++ b/packages/junior/tests/integration/api/plugin-reports.test.ts @@ -2,7 +2,8 @@ import { randomUUID } from "node:crypto"; import { afterEach, describe, expect, test, vi } from "vitest"; import { createJuniorApi } from "@/api"; import { pluginOperationalReportFeedSchema } from "@/api/schema"; -import { setBriefsConfig } from "@/chat/briefs/registration"; +import { briefsFeature } from "@/chat/briefs/task"; +import { setCoreFeatures } from "@/chat/plugins/core-features"; import { getDb } from "@/chat/db"; import { juniorConversationBriefs, @@ -21,7 +22,9 @@ describe("plugin reports API route", () => { test("uses one UTC calendar window for Brief count and cost metrics", async () => { vi.useFakeTimers({ now: new Date("2026-07-28T12:00:00.000Z") }); const fixture = createConfiguredJuniorSqlFixture(); - const previousBriefsConfig = setBriefsConfig({ enabled: true }); + const previousCoreFeatures = setCoreFeatures([ + briefsFeature({ enabled: true }), + ]); const windowStart = new Date("2026-06-29T00:00:00.000Z"); const reports = [ { @@ -126,7 +129,7 @@ describe("plugin reports API route", () => { values: { briefs: 1, costUsd: 0.0042 }, }); } finally { - setBriefsConfig(previousBriefsConfig); + setCoreFeatures(previousCoreFeatures); await fixture.close(); } }); diff --git a/packages/junior/tests/integration/api/user-pages.test.ts b/packages/junior/tests/integration/api/user-pages.test.ts index 56c5c82635..a20f4c7112 100644 --- a/packages/junior/tests/integration/api/user-pages.test.ts +++ b/packages/junior/tests/integration/api/user-pages.test.ts @@ -16,7 +16,7 @@ import { createConfiguredJuniorSqlFixture } from "../../fixtures/sql"; function plugin() { return defineJuniorPlugin({ manifest: { - name: "memory", + name: "notes", displayName: "Memory", description: "Long-term memory storage and recall.", }, @@ -72,7 +72,7 @@ describe("plugin user page API", () => { label: "Memories", navigation: "profile", pluginDisplayName: "Memory", - pluginName: "memory", + pluginName: "notes", }, ]); }); @@ -81,7 +81,7 @@ describe("plugin user page API", () => { setPlugins([plugin()]); const response = await createJuniorApi().request( - "http://localhost/api/user-pages/memory/memories", + "http://localhost/api/user-pages/notes/memories", ); expect(response.status).toBe(401); @@ -92,7 +92,7 @@ describe("plugin user page API", () => { setPlugins([ defineJuniorPlugin({ manifest: { - name: "memory", + name: "notes", displayName: "Memory", description: "Long-term memory storage and recall.", }, @@ -114,7 +114,7 @@ describe("plugin user page API", () => { ]); const response = await authenticatedApi("viewer@example.com").request( - "http://localhost/api/user-pages/memory/memories?q=runbooks&filter=preferences&cursor=next-page&limit=12", + "http://localhost/api/user-pages/notes/memories?q=runbooks&filter=preferences&cursor=next-page&limit=12", ); expect(response.status).toBe(200); @@ -126,13 +126,13 @@ describe("plugin user page API", () => { }); const emptyLimit = await authenticatedApi("viewer@example.com").request( - "http://localhost/api/user-pages/memory/memories?limit=", + "http://localhost/api/user-pages/notes/memories?limit=", ); expect(emptyLimit.status).toBe(200); expect(receivedInput).toEqual({ limit: 20 }); const invalid = await authenticatedApi("viewer@example.com").request( - "http://localhost/api/user-pages/memory/memories?limit=500", + "http://localhost/api/user-pages/notes/memories?limit=500", ); expect(invalid.status).toBe(400); }); @@ -177,7 +177,7 @@ describe("plugin user page API", () => { setPlugins([plugin()]); const response = await authenticatedApi("VIEWER@example.com").request( - "http://localhost/api/user-pages/memory/memories", + "http://localhost/api/user-pages/notes/memories", ); expect(response.status).toBe(200); diff --git a/packages/junior/tests/integration/conversation-sql.test.ts b/packages/junior/tests/integration/conversation-sql.test.ts index 7e29b6e79e..9776b9d841 100644 --- a/packages/junior/tests/integration/conversation-sql.test.ts +++ b/packages/junior/tests/integration/conversation-sql.test.ts @@ -19,6 +19,7 @@ import { createEmptyJuniorPostgresFixture, createEmptyJuniorSqlFixture, hasJuniorPostgresTestDatabase, + createUnextendedJuniorSqlFixture, } from "../fixtures/sql"; import { applyCoreMigrations as applyCoreMigrationSlice, @@ -799,6 +800,19 @@ WHERE conversation_id = $1 } }); + it("stops before migrating when required extensions are unavailable", async () => { + const fixture = await createUnextendedJuniorSqlFixture(); + + try { + await expect(migrateSchema(fixture.sql)).rejects.toThrow( + "Junior requires the Postgres extensions btree_gin and vector", + ); + await expectNoDrizzleMigrationState(fixture.sql); + } finally { + await fixture.close(); + } + }); + it.skipIf(!hasJuniorPostgresTestDatabase())( "requires the bridge release for existing pre-Drizzle tables", async () => { diff --git a/packages/junior/tests/integration/sandbox-egress-proxy.test.ts b/packages/junior/tests/integration/sandbox-egress-proxy.test.ts index 26e28f61a4..7e2d8a78a8 100644 --- a/packages/junior/tests/integration/sandbox-egress-proxy.test.ts +++ b/packages/junior/tests/integration/sandbox-egress-proxy.test.ts @@ -133,8 +133,9 @@ async function registerManagedEgressPlugin(input?: { onEgressResponse?: NonNullable; tools?: NonNullable; }) { - const { createApp, defineJuniorPlugins } = await import("@/app"); - await createApp({ + const { defineJuniorPlugins } = await import("@/app"); + const { createTestApp } = await import("../fixtures/app"); + await createTestApp({ plugins: defineJuniorPlugins([ defineJuniorPlugin({ manifest: { @@ -194,8 +195,9 @@ async function registerManagedEgressPlugin(input?: { } async function registerOAuthBrokerPlugin() { - const { createApp, defineJuniorPlugins } = await import("@/app"); - await createApp({ + const { defineJuniorPlugins } = await import("@/app"); + const { createTestApp } = await import("../fixtures/app"); + await createTestApp({ plugins: defineJuniorPlugins([ defineJuniorPlugin({ manifest: { @@ -229,9 +231,10 @@ async function registerOAuthBrokerPlugin() { async function registerGitHubPlugin( options?: Parameters[0], ) { - const { createApp, defineJuniorPlugins } = await import("@/app"); + const { defineJuniorPlugins } = await import("@/app"); + const { createTestApp } = await import("../fixtures/app"); const plugin = githubPlugin(options); - await createApp({ + await createTestApp({ plugins: defineJuniorPlugins([ { ...plugin, diff --git a/packages/junior/tests/unit/api/conversation-events.test.ts b/packages/junior/tests/unit/api/conversation-events.test.ts index e660027844..0770c082a7 100644 --- a/packages/junior/tests/unit/api/conversation-events.test.ts +++ b/packages/junior/tests/unit/api/conversation-events.test.ts @@ -64,16 +64,16 @@ describe("conversation report event projection", () => { const previous = setPlugins([ defineJuniorPlugin({ manifest: { - name: "memory", - displayName: "Memory", - description: "Memory test plugin", + name: "recall", + displayName: "Recall", + description: "Recall test plugin", }, conversationEvents: [captured], }), ]); const pluginEvent = event(1, { type: "structured_event", - namespace: "memory", + namespace: "recall", name: "memories_captured", version: 1, turnId: "turn-1", @@ -91,7 +91,7 @@ describe("conversation report event projection", () => { createdAt: new Date(1_000).toISOString(), data: { type: "structured_event", - namespace: "memory", + namespace: "recall", name: "memories_captured", version: 1, turnId: "turn-1", diff --git a/packages/junior/tests/unit/app-config.test.ts b/packages/junior/tests/unit/app-config.test.ts index 2b2c3a3442..e7db15d95f 100644 --- a/packages/junior/tests/unit/app-config.test.ts +++ b/packages/junior/tests/unit/app-config.test.ts @@ -783,7 +783,7 @@ describe("createApp plugin config", () => { it("forwards virtual plugin API route apps into dashboard setup", async () => { const pluginRouteApp = { - fetch: () => new Response("memory"), + fetch: () => new Response("notes"), }; const createDashboardApp = vi.fn( (options: { @@ -812,9 +812,9 @@ describe("createApp plugin config", () => { pluginSet: defineJuniorPlugins([ defineJuniorPlugin({ manifest: { - name: "memory", - displayName: "Memory", - description: "Memory plugin", + name: "notes", + displayName: "Notes", + description: "Notes plugin", }, hooks: { apiRoutes() { @@ -824,17 +824,17 @@ describe("createApp plugin config", () => { }), ]), plugins: undefined, - pluginRuntimeRegistrations: ["memory"], + pluginRuntimeRegistrations: ["notes"], })); const app = await createApp(); const response = await app.fetch( - new Request("http://localhost/api/plugins/memory"), + new Request("http://localhost/api/plugins/notes"), ); expect(response.status).toBe(200); - await expect(response.text()).resolves.toBe("memory"); + await expect(response.text()).resolves.toBe("notes"); const peoplePage = await app.fetch(new Request("http://localhost/people")); expect(peoplePage.status).toBe(200); diff --git a/packages/junior/tests/unit/cli/init-cli.test.ts b/packages/junior/tests/unit/cli/init-cli.test.ts index 89d263ce25..4262abc681 100644 --- a/packages/junior/tests/unit/cli/init-cli.test.ts +++ b/packages/junior/tests/unit/cli/init-cli.test.ts @@ -151,11 +151,8 @@ describe("init cli", () => { expect(pluginsFile).toContain( 'import { defineJuniorPlugins } from "@sentry/junior";', ); - expect(pluginsFile).toContain( - 'import { memoryPlugin } from "@sentry/junior-memory";', - ); + expect(pluginsFile).not.toContain("@sentry/junior-memory"); expect(pluginsFile).toContain("defineJuniorPlugins("); - expect(pluginsFile).toContain("memoryPlugin()"); expect(pluginsFile).toContain('"@sentry/junior-maintenance"'); const pkg = readJsonFile<{ @@ -164,7 +161,7 @@ describe("init cli", () => { scripts: Record; }>(path.join(target, "package.json")); expect(pkg.dependencies["@sentry/junior"]).toBe("latest"); - expect(pkg.dependencies["@sentry/junior-memory"]).toBe("latest"); + expect(pkg.dependencies["@sentry/junior-memory"]).toBeUndefined(); expect(pkg.dependencies["@sentry/junior-maintenance"]).toBe("latest"); expect(pkg.dependencies["@sentry/nitro"]).toBe("10.65.0"); expect(pkg.devDependencies.nitro).toBeDefined(); diff --git a/packages/junior/tests/unit/cli/plugin-commands.test.ts b/packages/junior/tests/unit/cli/plugin-commands.test.ts index 7ee8c2cbb3..0a6d6d3631 100644 --- a/packages/junior/tests/unit/cli/plugin-commands.test.ts +++ b/packages/junior/tests/unit/cli/plugin-commands.test.ts @@ -110,15 +110,15 @@ describe("plugin CLI commands", () => { pluginSetRef.current = defineJuniorPlugins([ defineJuniorPlugin({ manifest: { - name: "memory", - displayName: "Memory", - description: "Memory plugin", + name: "notes", + displayName: "Notes", + description: "Notes plugin", }, cli: { commands: [ { - name: "memory", - summary: "Memory command", + name: "notes", + summary: "Notes command", configure(command) { command.name("renamed"); command.command("search"); @@ -130,7 +130,7 @@ describe("plugin CLI commands", () => { ]); await expect(loadCliPluginCommands()).rejects.toThrow( - 'Plugin CLI command "memory" from plugin "memory" must not rename its top-level command', + 'Plugin CLI command "notes" from plugin "notes" must not rename its top-level command', ); }); @@ -138,15 +138,15 @@ describe("plugin CLI commands", () => { pluginSetRef.current = defineJuniorPlugins([ defineJuniorPlugin({ manifest: { - name: "memory", - displayName: "Memory", - description: "Memory plugin", + name: "notes", + displayName: "Notes", + description: "Notes plugin", }, cli: { commands: [ { - name: "memory", - summary: "Memory command", + name: "notes", + summary: "Notes command", configure(command) { command.alias("mem"); command.command("search"); @@ -158,7 +158,7 @@ describe("plugin CLI commands", () => { ]); await expect(loadCliPluginCommands()).rejects.toThrow( - 'Plugin CLI command "memory" from plugin "memory" must not define top-level aliases', + 'Plugin CLI command "notes" from plugin "notes" must not define top-level aliases', ); }); @@ -252,7 +252,7 @@ describe("plugin CLI commands", () => { cli: { commands: [ { - name: "memory", + name: "notes", summary: "First memory", configure(command) { command.command("search"); @@ -270,7 +270,7 @@ describe("plugin CLI commands", () => { cli: { commands: [ { - name: "memory", + name: "notes", summary: "Second memory", configure(command) { command.command("show"); @@ -282,7 +282,7 @@ describe("plugin CLI commands", () => { ]); await expect(loadCliPluginCommands()).rejects.toThrow( - 'Plugin CLI command "memory" from plugin "second" conflicts with plugin "first"', + 'Plugin CLI command "notes" from plugin "second" conflicts with plugin "first"', ); }); diff --git a/packages/junior-memory/tests/events.test.ts b/packages/junior/tests/unit/memory/events.test.ts similarity index 98% rename from packages/junior-memory/tests/events.test.ts rename to packages/junior/tests/unit/memory/events.test.ts index eb91722c67..8a7c544179 100644 --- a/packages/junior-memory/tests/events.test.ts +++ b/packages/junior/tests/unit/memory/events.test.ts @@ -3,7 +3,7 @@ import { memoriesCapturedEvent, memoriesCapturedEventV1, memoriesRecalledEvent, -} from "../src/events"; +} from "@/chat/memory/events"; describe("memory conversation events", () => { it("renders stored capture events with legacy scope values", () => { diff --git a/packages/junior-memory/tests/ranking.test.ts b/packages/junior/tests/unit/memory/ranking.test.ts similarity index 88% rename from packages/junior-memory/tests/ranking.test.ts rename to packages/junior/tests/unit/memory/ranking.test.ts index cfdbf8ec1b..a8bd507021 100644 --- a/packages/junior-memory/tests/ranking.test.ts +++ b/packages/junior/tests/unit/memory/ranking.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { rankMemoryMatches, type MemoryMatch } from "../src/ranking"; -import type { MemoryRecord } from "../src/store"; +import { rankMemoryMatches, type MemoryMatch } from "@/chat/memory/ranking"; +import type { MemoryRecord } from "@/chat/memory/store"; const NOW_MS = Date.parse("2026-07-28T12:00:00.000Z"); @@ -32,8 +32,8 @@ describe("memory retrieval ranking", () => { it("promotes a memory supported by both retrieval methods", () => { const ranked = rankMemoryMatches( [ - match("vector-only", { vector: { distance: 0.1, rank: 1 } }), - match("supported", { vector: { distance: 0.2, rank: 2 } }), + match("vector-only", { vector: { rank: 1 } }), + match("supported", { vector: { rank: 2 } }), match("supported", { lexical: { rank: 2 } }), match("lexical-only", { lexical: { rank: 1 } }), ], diff --git a/packages/junior/tests/unit/plugins/agent-hooks.test.ts b/packages/junior/tests/unit/plugins/agent-hooks.test.ts index eaeae387ac..264436c366 100644 --- a/packages/junior/tests/unit/plugins/agent-hooks.test.ts +++ b/packages/junior/tests/unit/plugins/agent-hooks.test.ts @@ -430,9 +430,9 @@ describe("agent plugin hooks", () => { const previous = setPlugins([ defineJuniorPlugin({ manifest: { - name: "memory", - displayName: "Memory", - description: "Memory", + name: "recall", + displayName: "Recall", + description: "Recall", }, hooks: { userPrompt() { @@ -458,7 +458,7 @@ describe("agent plugin hooks", () => { ).resolves.toEqual([ { id: "userPrompt:0", - pluginName: "memory", + pluginName: "recall", text: "Use pnpm.", context: { content: { @@ -466,7 +466,7 @@ describe("agent plugin hooks", () => { }, kind: "recall", loadedAtMs: expect.any(Number), - pluginName: "memory", + pluginName: "recall", version: 1, }, }, diff --git a/packages/junior/tests/unit/plugins/core-features.test.ts b/packages/junior/tests/unit/plugins/core-features.test.ts new file mode 100644 index 0000000000..94ceaba302 --- /dev/null +++ b/packages/junior/tests/unit/plugins/core-features.test.ts @@ -0,0 +1,104 @@ +import { defineJuniorPlugin } from "@sentry/junior-plugin-api"; +import { afterEach, describe, expect, it } from "vitest"; +import { createCoreFeatures } from "@/chat/app/core-features"; +import { + getRegistrations, + setPlugins, + validatePlugins, +} from "@/chat/plugins/agent-hooks"; +import { getCoreFeatures, setCoreFeatures } from "@/chat/plugins/core-features"; +import { createPluginCatalogRuntime } from "@/chat/plugins/registry"; +import { + defineJuniorPlugins, + pluginCatalogConfigFromPluginSet, +} from "@/plugins"; + +afterEach(() => { + setCoreFeatures([]); + setPlugins([]); +}); + +describe("core features", () => { + it("serves core features before plugins in one registration list", () => { + setCoreFeatures(createCoreFeatures({ briefs: { enabled: true } })); + setPlugins([ + defineJuniorPlugin({ + manifest: { + name: "acme", + displayName: "Acme", + description: "Test plugin", + }, + hooks: {}, + }), + ]); + + expect(getRegistrations().map((entry) => entry.manifest.name)).toEqual([ + "briefs", + "memory", + "acme", + ]); + expect(getCoreFeatures()[0]?.tasks).toHaveProperty("updateBrief"); + }); + + it("keeps a disabled core feature limited to its event definitions", () => { + const [briefs] = createCoreFeatures(); + + expect(briefs?.conversationEvents?.map((event) => event.eventName)).toEqual( + ["brief_updated"], + ); + expect(briefs?.tasks).toBeUndefined(); + expect(briefs?.hooks).toBeUndefined(); + }); + + it("rejects registrations that are not reserved core feature names", () => { + const acme = defineJuniorPlugin({ + manifest: { + name: "acme", + displayName: "Acme", + description: "Test plugin", + }, + }); + + expect(() => setCoreFeatures([acme])).toThrow( + 'Core feature name "acme" is not reserved for core', + ); + const [briefs] = createCoreFeatures(); + expect(() => setCoreFeatures([briefs!, briefs!])).toThrow( + 'Duplicate core feature "briefs"', + ); + }); + + it("rejects plugins that claim a core feature name", () => { + const plugin = defineJuniorPlugin({ + manifest: { + name: "briefs", + displayName: "Briefs", + description: "Impostor", + }, + hooks: {}, + }); + + expect(() => validatePlugins([plugin])).toThrow( + 'Plugin name "briefs" is reserved for a Junior core feature', + ); + + // Manifest-only plugins skip runtime validation and enter the catalog. + const catalog = createPluginCatalogRuntime(); + catalog.setConfig( + pluginCatalogConfigFromPluginSet( + defineJuniorPlugins([ + defineJuniorPlugin({ + manifest: { + name: "memory", + displayName: "Other Memory", + description: "Not core memory", + }, + }), + ]), + ), + ); + expect(() => catalog.getProviders()).toThrow( + 'Plugin name "memory" is reserved for a Junior core feature', + ); + }); +}); diff --git a/packages/junior/tests/unit/plugins/user-pages.test.ts b/packages/junior/tests/unit/plugins/user-pages.test.ts index 998d2d12aa..dc7dc4fa5b 100644 --- a/packages/junior/tests/unit/plugins/user-pages.test.ts +++ b/packages/junior/tests/unit/plugins/user-pages.test.ts @@ -10,81 +10,81 @@ describe("plugin user pages", () => { expect(() => defineJuniorPlugin({ manifest: { - name: "memory", - displayName: "Memory", - description: "Memory plugin", + name: "notes", + displayName: "Notes", + description: "Notes plugin", }, userPages: [ { - id: "Memories", - label: "Memories", - description: "Personal memories.", + id: "Notes", + label: "Notes", + description: "Personal notes.", read, }, ], }), ).toThrow( - 'Junior plugin "memory" user page id "Memories" must be a lowercase identifier.', + 'Junior plugin "notes" user page id "Notes" must be a lowercase identifier.', ); expect(() => defineJuniorPlugin({ manifest: { - name: "memory", - displayName: "Memory", - description: "Memory plugin", + name: "notes", + displayName: "Notes", + description: "Notes plugin", }, userPages: [ { - id: "memories", - label: "Memories", - description: "Personal memories.", + id: "notes", + label: "Notes", + description: "Personal notes.", read, }, { - id: "memories", + id: "notes", label: "Other memories", description: "More personal memories.", read, }, ], }), - ).toThrow('Junior plugin "memory" has duplicate user page id "memories".'); + ).toThrow('Junior plugin "notes" has duplicate user page id "notes".'); expect(() => defineJuniorPlugin({ manifest: { - name: "memory", - displayName: "Memory", - description: "Memory plugin", + name: "notes", + displayName: "Notes", + description: "Notes plugin", }, userPages: [ { - id: "memories", - label: "Memories", - description: "Personal memories.", + id: "notes", + label: "Notes", + description: "Personal notes.", navigation: "sidebar" as "primary", read, }, ], }), ).toThrow( - 'Junior plugin "memory" user page "memories" navigation must be "primary" or "profile".', + 'Junior plugin "notes" user page "notes" navigation must be "primary" or "profile".', ); }); it("keeps page-only plugins in the runtime plugin set", () => { const plugin = defineJuniorPlugin({ manifest: { - name: "memory", - displayName: "Memory", - description: "Memory plugin", + name: "notes", + displayName: "Notes", + description: "Notes plugin", }, userPages: [ { - id: "memories", - label: "Memories", - description: "Personal memories.", + id: "notes", + label: "Notes", + description: "Personal notes.", read: () => ({ type: "list", records: [] }), }, ], diff --git a/packages/junior/tsdown.config.ts b/packages/junior/tsdown.config.ts index 3506b74e79..36021b0e13 100644 --- a/packages/junior/tsdown.config.ts +++ b/packages/junior/tsdown.config.ts @@ -14,6 +14,7 @@ export default defineConfig({ "cli/snapshot-create": "src/cli/snapshot-create.ts", api: "src/api.ts", "api/schema": "src/api/schema.ts", + memory: "src/memory.ts", instrumentation: "src/instrumentation.ts", nitro: "src/nitro.ts", vercel: "src/vercel.ts", diff --git a/packages/junior/vitest.config.ts b/packages/junior/vitest.config.ts index e73b041641..06328a0f04 100644 --- a/packages/junior/vitest.config.ts +++ b/packages/junior/vitest.config.ts @@ -20,10 +20,6 @@ export default defineConfig({ __dirname, "../junior-plugin-api/src/index.ts", ), - "@sentry/junior-memory": path.resolve( - __dirname, - "../junior-memory/src/index.ts", - ), }, }, test: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd7115ba00..e066bf63ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,9 +121,6 @@ importers: '@sentry/junior-linear': specifier: workspace:* version: link:../../packages/junior-linear - '@sentry/junior-memory': - specifier: workspace:* - version: file:packages/junior-memory(@opentelemetry/api@1.9.1) '@sentry/junior-notion': specifier: workspace:* version: link:../../packages/junior-notion @@ -294,9 +291,6 @@ importers: '@sentry/junior-github': specifier: workspace:* version: file:packages/junior-github(@neondatabase/serverless@1.1.0)(@opentelemetry/api@1.9.1)(@types/pg@8.15.6)(pg@8.21.0) - '@sentry/junior-memory': - specifier: workspace:* - version: file:packages/junior-memory(@neondatabase/serverless@1.1.0)(@opentelemetry/api@1.9.1)(@types/pg@8.15.6)(pg@8.21.0) '@sentry/junior-testing': specifier: workspace:* version: file:packages/junior-testing(@neondatabase/serverless@1.1.0)(@opentelemetry/api@1.9.1)(@types/pg@8.15.6) @@ -444,9 +438,6 @@ importers: '@sentry/junior-github': specifier: workspace:* version: file:packages/junior-github - '@sentry/junior-memory': - specifier: workspace:* - version: file:packages/junior-memory '@sentry/junior-plugin-api': specifier: workspace:* version: link:../junior-plugin-api @@ -581,49 +572,6 @@ importers: packages/junior-maintenance: {} - packages/junior-memory: - dependencies: - '@sentry/junior-plugin-api': - specifier: workspace:* - version: link:../junior-plugin-api - '@sinclair/typebox': - specifier: ^0.34.49 - version: 0.34.49 - commander: - specifier: ^14.0.3 - version: 14.0.3 - drizzle-orm: - specifier: 'catalog:' - version: 0.45.2 - zod: - specifier: 'catalog:' - version: 4.5.4 - devDependencies: - '@oxlint/plugins': - specifier: 1.79.0 - version: 1.79.0 - '@sentry/junior-testing': - specifier: workspace:* - version: file:packages/junior-testing - '@types/node': - specifier: ^25.9.1 - version: 25.9.1 - drizzle-kit: - specifier: 'catalog:' - version: 0.31.10 - oxlint: - specifier: ^1.79.0 - version: 1.79.0 - tsup: - specifier: ^8.5.1 - version: 8.5.1(tsx@4.22.3)(typescript@6.0.3) - typescript: - specifier: ^6.0.3 - version: 6.0.3 - vitest: - specifier: ^4.1.7 - version: 4.1.7(@types/node@25.9.1)(tsx@4.22.3) - packages/junior-notion: {} packages/junior-octolens: {} @@ -3828,9 +3776,6 @@ packages: '@sentry/junior-github@file:packages/junior-github': resolution: {directory: packages/junior-github, type: directory} - '@sentry/junior-memory@file:packages/junior-memory': - resolution: {directory: packages/junior-memory, type: directory} - '@sentry/junior-plugin-api@file:packages/junior-plugin-api': resolution: {directory: packages/junior-plugin-api, type: directory} @@ -11964,120 +11909,6 @@ snapshots: - sql.js - sqlite3 - '@sentry/junior-memory@file:packages/junior-memory': - dependencies: - '@sentry/junior-plugin-api': file:packages/junior-plugin-api - '@sinclair/typebox': 0.34.52 - commander: 14.0.3 - drizzle-orm: 0.45.2 - zod: 4.5.4 - transitivePeerDependencies: - - '@aws-sdk/client-rds-data' - - '@cloudflare/workers-types' - - '@electric-sql/pglite' - - '@libsql/client' - - '@libsql/client-wasm' - - '@neondatabase/serverless' - - '@op-engineering/op-sqlite' - - '@opentelemetry/api' - - '@planetscale/database' - - '@prisma/client' - - '@tidbcloud/serverless' - - '@types/better-sqlite3' - - '@types/pg' - - '@types/sql.js' - - '@upstash/redis' - - '@vercel/postgres' - - '@xata.io/client' - - better-sqlite3 - - bun-types - - expo-sqlite - - gel - - knex - - kysely - - mysql2 - - pg - - postgres - - prisma - - sql.js - - sqlite3 - - '@sentry/junior-memory@file:packages/junior-memory(@neondatabase/serverless@1.1.0)(@opentelemetry/api@1.9.1)(@types/pg@8.15.6)(pg@8.21.0)': - dependencies: - '@sentry/junior-plugin-api': file:packages/junior-plugin-api - '@sinclair/typebox': 0.34.52 - commander: 14.0.3 - drizzle-orm: 0.45.2(@neondatabase/serverless@1.1.0)(@opentelemetry/api@1.9.1)(@types/pg@8.15.6)(pg@8.21.0) - zod: 4.5.4 - transitivePeerDependencies: - - '@aws-sdk/client-rds-data' - - '@cloudflare/workers-types' - - '@electric-sql/pglite' - - '@libsql/client' - - '@libsql/client-wasm' - - '@neondatabase/serverless' - - '@op-engineering/op-sqlite' - - '@opentelemetry/api' - - '@planetscale/database' - - '@prisma/client' - - '@tidbcloud/serverless' - - '@types/better-sqlite3' - - '@types/pg' - - '@types/sql.js' - - '@upstash/redis' - - '@vercel/postgres' - - '@xata.io/client' - - better-sqlite3 - - bun-types - - expo-sqlite - - gel - - knex - - kysely - - mysql2 - - pg - - postgres - - prisma - - sql.js - - sqlite3 - - '@sentry/junior-memory@file:packages/junior-memory(@opentelemetry/api@1.9.1)': - dependencies: - '@sentry/junior-plugin-api': file:packages/junior-plugin-api - '@sinclair/typebox': 0.34.52 - commander: 14.0.3 - drizzle-orm: 0.45.2(@opentelemetry/api@1.9.1) - zod: 4.5.4 - transitivePeerDependencies: - - '@aws-sdk/client-rds-data' - - '@cloudflare/workers-types' - - '@electric-sql/pglite' - - '@libsql/client' - - '@libsql/client-wasm' - - '@neondatabase/serverless' - - '@op-engineering/op-sqlite' - - '@opentelemetry/api' - - '@planetscale/database' - - '@prisma/client' - - '@tidbcloud/serverless' - - '@types/better-sqlite3' - - '@types/pg' - - '@types/sql.js' - - '@upstash/redis' - - '@vercel/postgres' - - '@xata.io/client' - - better-sqlite3 - - bun-types - - expo-sqlite - - gel - - knex - - kysely - - mysql2 - - pg - - postgres - - prisma - - sql.js - - sqlite3 - '@sentry/junior-plugin-api@file:packages/junior-plugin-api': dependencies: commander: 14.0.3 diff --git a/scripts/bump-release-versions.mjs b/scripts/bump-release-versions.mjs index 197b8e1637..1335caf272 100644 --- a/scripts/bump-release-versions.mjs +++ b/scripts/bump-release-versions.mjs @@ -20,7 +20,6 @@ const files = [ "packages/junior-gocd/package.json", "packages/junior-hex/package.json", "packages/junior-linear/package.json", - "packages/junior-memory/package.json", "packages/junior-notion/package.json", "packages/junior-octolens/package.json", "packages/junior-maintenance/package.json", diff --git a/scripts/check-package-types.sh b/scripts/check-package-types.sh index 4c5dd8a1c5..9b44b121eb 100644 --- a/scripts/check-package-types.sh +++ b/scripts/check-package-types.sh @@ -11,7 +11,6 @@ for package_name in \ junior-github \ junior-gocd \ junior-linear \ - junior-memory \ junior-plugin-api \ junior-sentry \ junior-vercel diff --git a/scripts/check-test-architecture.mjs b/scripts/check-test-architecture.mjs index a9d307edde..0d4759d958 100644 --- a/scripts/check-test-architecture.mjs +++ b/scripts/check-test-architecture.mjs @@ -13,7 +13,8 @@ const DATABASE_TEST_ROOTS = [ const DASHBOARD_E2E_ROOT = "packages/junior-dashboard/e2e/"; const EMPTY_DATABASE_TESTS = new Set([ "packages/junior/tests/component/conversation-storage-sql.test.ts", - "packages/junior/tests/component/memory-plugin-storage.test.ts", + "packages/junior/tests/component/memory/host-wiring.test.ts", + "packages/junior/tests/component/plugins/plugin-migrations.test.ts", "packages/junior/tests/component/scheduled-automations-sql.test.ts", "packages/junior/tests/integration/conversation-sql.test.ts", "packages/junior/tests/integration/workspace-snapshot-migration.test.ts", diff --git a/scripts/file-length-exceptions.mjs b/scripts/file-length-exceptions.mjs index b1219cb97e..c5f7c3fa9f 100644 --- a/scripts/file-length-exceptions.mjs +++ b/scripts/file-length-exceptions.mjs @@ -16,9 +16,9 @@ export const fileLengthExceptions = { "Existing broad plugin suite; split with plugin modules.", "packages/junior-github/tests/webhook-outcomes.test.ts": "Existing broad webhook outcome suite; split by outcome.", - "packages/junior-memory/src/store.ts": + "packages/junior/src/chat/memory/store.ts": "Existing memory store; split by storage concern.", - "packages/junior-memory/tests/storage.test.ts": + "packages/junior/tests/component/memory/storage.test.ts": "Existing broad memory storage suite; split by storage concern.", "packages/junior/src/chat/agent/index.ts": "Existing agent run lifecycle; split only at a clear lifecycle boundary.",