diff --git a/.env.local.example b/.env.local.example
index 13078722..097692fe 100644
--- a/.env.local.example
+++ b/.env.local.example
@@ -1,11 +1,17 @@
-# Optional API base URL. Defaults to production when unset.
-# Use staging when validating staging keys.
-# KNOWHERE_BASE_URL=https://api-staging.knowhereto.ai
+# Knowhere API origin. Unset also means production.
+# Local recommendation: production. Staging is only for staging keys.
+KNOWHERE_BASE_URL=https://api.knowhereto.ai
-# Optional development override. When set, Notebook skips Dashboard session
+# Local development override. When set, Notebook skips Dashboard session
# auth and Dashboard-issued JWT creation, then calls Knowhere directly with
# this key. Leave unset for production and Dashboard-authenticated staging.
-# KNOWHERE_API_KEY=sk_your_development_key_here
+# KNOWHERE_API_KEY=
+
+# --- Local demo case only (观心). Not Notebook core. ---
+# Put the real key in gitignored `.env.local`, copied from
+# `观心2.0-RAG-v1.1-demo/_retrieval/.env`.
+# Do not copy that file's KNOWHERE_BASE_URL — it is a retrieval endpoint
+# path, not the SDK origin above.
# --- Chat provider (server-side only) ---
# Vercel AI Gateway key; AI SDK picks it up automatically
@@ -61,3 +67,8 @@ DATABASE_URL=postgres://user:password@host/db
# pg — postgres-js (use for local dev against a plain Postgres,
# or for AWS Aurora Postgres if/when we migrate off Neon)
DATABASE_DRIVER=pg
+
+# --- Memento (fluid memory + retrieval activation) ---
+# Standalone REST + MCP service. Notebook no longer owns these tables.
+MEMENTO_BASE_URL=http://localhost:8787
+MEMENTO_SERVICE_KEY=
diff --git a/.pnpm-store/v11/index.db b/.pnpm-store/v11/index.db
new file mode 100644
index 00000000..8fdf9e7d
Binary files /dev/null and b/.pnpm-store/v11/index.db differ
diff --git a/.repos/OpenViking b/.repos/OpenViking
new file mode 160000
index 00000000..f6d9dec6
--- /dev/null
+++ b/.repos/OpenViking
@@ -0,0 +1 @@
+Subproject commit f6d9dec6b6ae16a152c437fd4ad81ca45fcc8648
diff --git a/drizzle/0015_bumpy_vulcan.sql b/drizzle/0015_bumpy_vulcan.sql
new file mode 100644
index 00000000..2b383759
--- /dev/null
+++ b/drizzle/0015_bumpy_vulcan.sql
@@ -0,0 +1,17 @@
+CREATE TABLE "fluid_observations" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "workspace_id" uuid NOT NULL,
+ "source_message_id" uuid,
+ "signal" text NOT NULL,
+ "evidence_quote" text NOT NULL,
+ "subject_hint" text,
+ "referenced_document_ids" jsonb DEFAULT '[]'::jsonb NOT NULL,
+ "confidence" double precision NOT NULL,
+ "status" text DEFAULT 'pending' NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "consumed_at" timestamp with time zone
+);
+--> statement-breakpoint
+ALTER TABLE "fluid_observations" ADD CONSTRAINT "fluid_observations_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "fluid_observations" ADD CONSTRAINT "fluid_observations_source_message_id_chat_messages_id_fk" FOREIGN KEY ("source_message_id") REFERENCES "public"."chat_messages"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+CREATE INDEX "fluid_observations_workspace_status_created_idx" ON "fluid_observations" USING btree ("workspace_id","status","created_at");
\ No newline at end of file
diff --git a/drizzle/0016_wealthy_matthew_murdock.sql b/drizzle/0016_wealthy_matthew_murdock.sql
new file mode 100644
index 00000000..4d7a57ec
--- /dev/null
+++ b/drizzle/0016_wealthy_matthew_murdock.sql
@@ -0,0 +1,13 @@
+CREATE TABLE "retrieval_activations" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "workspace_id" uuid NOT NULL,
+ "unit_type" text NOT NULL,
+ "unit_ref" text NOT NULL,
+ "activation_count" integer DEFAULT 0 NOT NULL,
+ "last_activated_at" timestamp with time zone,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "fluid_memory_items" ADD COLUMN "deactivation_reason" text;--> statement-breakpoint
+ALTER TABLE "retrieval_activations" ADD CONSTRAINT "retrieval_activations_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+CREATE UNIQUE INDEX "retrieval_activations_unit_idx" ON "retrieval_activations" USING btree ("workspace_id","unit_type","unit_ref");
\ No newline at end of file
diff --git a/drizzle/0017_overjoyed_shooting_star.sql b/drizzle/0017_overjoyed_shooting_star.sql
new file mode 100644
index 00000000..6d97d3ac
--- /dev/null
+++ b/drizzle/0017_overjoyed_shooting_star.sql
@@ -0,0 +1,5 @@
+DROP TABLE "fluid_memory_items" CASCADE;--> statement-breakpoint
+DROP TABLE "fluid_memory_tokens" CASCADE;--> statement-breakpoint
+DROP TABLE "fluid_observations" CASCADE;--> statement-breakpoint
+DROP TABLE "memory_diffs" CASCADE;--> statement-breakpoint
+DROP TABLE "retrieval_activations" CASCADE;
\ No newline at end of file
diff --git a/drizzle/meta/0015_snapshot.json b/drizzle/meta/0015_snapshot.json
new file mode 100644
index 00000000..3b313fd5
--- /dev/null
+++ b/drizzle/meta/0015_snapshot.json
@@ -0,0 +1,1444 @@
+{
+ "id": "be8deb1f-3ec8-4304-85d0-41403629970c",
+ "prevId": "72bfb4fe-4048-4dce-a5fa-ce8eea880aa3",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.chat_messages": {
+ "name": "chat_messages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "citations": {
+ "name": "citations",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "artifacts": {
+ "name": "artifacts",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "chat_messages_thread_created_idx": {
+ "name": "chat_messages_thread_created_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "chat_messages_thread_id_chat_threads_id_fk": {
+ "name": "chat_messages_thread_id_chat_threads_id_fk",
+ "tableFrom": "chat_messages",
+ "tableTo": "chat_threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.chat_threads": {
+ "name": "chat_threads",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "demo_key": {
+ "name": "demo_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "chat_threads_workspace_updated_idx": {
+ "name": "chat_threads_workspace_updated_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "deleted_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "chat_threads_workspace_demo_key_idx": {
+ "name": "chat_threads_workspace_demo_key_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "demo_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "chat_threads_workspace_id_workspaces_id_fk": {
+ "name": "chat_threads_workspace_id_workspaces_id_fk",
+ "tableFrom": "chat_threads",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.demo_source_visibilities": {
+ "name": "demo_source_visibilities",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "demo_source_id": {
+ "name": "demo_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hidden_at": {
+ "name": "hidden_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "demo_source_visibilities_workspace_source_idx": {
+ "name": "demo_source_visibilities_workspace_source_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "demo_source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "demo_source_visibilities_workspace_idx": {
+ "name": "demo_source_visibilities_workspace_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "demo_source_visibilities_workspace_id_workspaces_id_fk": {
+ "name": "demo_source_visibilities_workspace_id_workspaces_id_fk",
+ "tableFrom": "demo_source_visibilities",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.fluid_memory_items": {
+ "name": "fluid_memory_items",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "abstract_l0": {
+ "name": "abstract_l0",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "overview_l1": {
+ "name": "overview_l1",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_message_id": {
+ "name": "source_message_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confidence": {
+ "name": "confidence",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "fluid_memory_items_workspace_status_idx": {
+ "name": "fluid_memory_items_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": {}
+ },
+ "fluid_memory_items_workspace_kind_idx": {
+ "name": "fluid_memory_items_workspace_kind_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "fluid_memory_items_workspace_id_workspaces_id_fk": {
+ "name": "fluid_memory_items_workspace_id_workspaces_id_fk",
+ "tableFrom": "fluid_memory_items",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "fluid_memory_items_source_message_id_chat_messages_id_fk": {
+ "name": "fluid_memory_items_source_message_id_chat_messages_id_fk",
+ "tableFrom": "fluid_memory_items",
+ "tableTo": "chat_messages",
+ "columnsFrom": [
+ "source_message_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.fluid_memory_tokens": {
+ "name": "fluid_memory_tokens",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "item_id": {
+ "name": "item_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "frequency": {
+ "name": "frequency",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ }
+ },
+ "indexes": {
+ "fluid_memory_tokens_lookup_idx": {
+ "name": "fluid_memory_tokens_lookup_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "fluid_memory_tokens_item_idx": {
+ "name": "fluid_memory_tokens_item_idx",
+ "columns": [
+ {
+ "expression": "item_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "fluid_memory_tokens_workspace_id_workspaces_id_fk": {
+ "name": "fluid_memory_tokens_workspace_id_workspaces_id_fk",
+ "tableFrom": "fluid_memory_tokens",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "fluid_memory_tokens_item_id_fluid_memory_items_id_fk": {
+ "name": "fluid_memory_tokens_item_id_fluid_memory_items_id_fk",
+ "tableFrom": "fluid_memory_tokens",
+ "tableTo": "fluid_memory_items",
+ "columnsFrom": [
+ "item_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.fluid_observations": {
+ "name": "fluid_observations",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_message_id": {
+ "name": "source_message_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "signal": {
+ "name": "signal",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "evidence_quote": {
+ "name": "evidence_quote",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subject_hint": {
+ "name": "subject_hint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "referenced_document_ids": {
+ "name": "referenced_document_ids",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "confidence": {
+ "name": "confidence",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "fluid_observations_workspace_status_created_idx": {
+ "name": "fluid_observations_workspace_status_created_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "fluid_observations_workspace_id_workspaces_id_fk": {
+ "name": "fluid_observations_workspace_id_workspaces_id_fk",
+ "tableFrom": "fluid_observations",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "fluid_observations_source_message_id_chat_messages_id_fk": {
+ "name": "fluid_observations_source_message_id_chat_messages_id_fk",
+ "tableFrom": "fluid_observations",
+ "tableTo": "chat_messages",
+ "columnsFrom": [
+ "source_message_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.memory_diffs": {
+ "name": "memory_diffs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_message_id": {
+ "name": "source_message_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "operations": {
+ "name": "operations",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "memory_diffs_workspace_created_idx": {
+ "name": "memory_diffs_workspace_created_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "memory_diffs_workspace_id_workspaces_id_fk": {
+ "name": "memory_diffs_workspace_id_workspaces_id_fk",
+ "tableFrom": "memory_diffs",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "memory_diffs_source_message_id_chat_messages_id_fk": {
+ "name": "memory_diffs_source_message_id_chat_messages_id_fk",
+ "tableFrom": "memory_diffs",
+ "tableTo": "chat_messages",
+ "columnsFrom": [
+ "source_message_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.parsed_document_sync_leases": {
+ "name": "parsed_document_sync_leases",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "document_id": {
+ "name": "document_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revision_key": {
+ "name": "revision_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lease_token": {
+ "name": "lease_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "acquired_at": {
+ "name": "acquired_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "released_at": {
+ "name": "released_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "release_reason": {
+ "name": "release_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "parsed_document_sync_leases_token_idx": {
+ "name": "parsed_document_sync_leases_token_idx",
+ "columns": [
+ {
+ "expression": "lease_token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "parsed_document_sync_leases_active_idx": {
+ "name": "parsed_document_sync_leases_active_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "released_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "parsed_document_sync_leases_workspace_active_idx": {
+ "name": "parsed_document_sync_leases_workspace_active_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "released_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "parsed_document_sync_leases_document_active_idx": {
+ "name": "parsed_document_sync_leases_document_active_idx",
+ "columns": [
+ {
+ "expression": "document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "released_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "parsed_document_sync_leases_workspace_id_workspaces_id_fk": {
+ "name": "parsed_document_sync_leases_workspace_id_workspaces_id_fk",
+ "tableFrom": "parsed_document_sync_leases",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "parsed_document_sync_leases_source_id_sources_id_fk": {
+ "name": "parsed_document_sync_leases_source_id_sources_id_fk",
+ "tableFrom": "parsed_document_sync_leases",
+ "tableTo": "sources",
+ "columnsFrom": [
+ "source_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.source_parse_results": {
+ "name": "source_parse_results",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "result_blob_url": {
+ "name": "result_blob_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "snapshot_manifest_url": {
+ "name": "snapshot_manifest_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "snapshot_manifest_key": {
+ "name": "snapshot_manifest_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revision_key": {
+ "name": "revision_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "asset_urls": {
+ "name": "asset_urls",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "source_parse_results_source_id_idx": {
+ "name": "source_parse_results_source_id_idx",
+ "columns": [
+ {
+ "expression": "source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "source_parse_results_source_id_sources_id_fk": {
+ "name": "source_parse_results_source_id_sources_id_fk",
+ "tableFrom": "source_parse_results",
+ "tableTo": "sources",
+ "columnsFrom": [
+ "source_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "source_parse_results_source_id_unique": {
+ "name": "source_parse_results_source_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "source_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sources": {
+ "name": "sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size_bytes": {
+ "name": "size_bytes",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "failure_reason": {
+ "name": "failure_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "failure_stage": {
+ "name": "failure_stage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "knowhere_job_id": {
+ "name": "knowhere_job_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "knowhere_document_id": {
+ "name": "knowhere_document_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "staged_blob_pathname": {
+ "name": "staged_blob_pathname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "staged_blob_url": {
+ "name": "staged_blob_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_blob_pathname": {
+ "name": "original_blob_pathname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_blob_url": {
+ "name": "original_blob_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "demo_key": {
+ "name": "demo_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "sources_workspace_created_idx": {
+ "name": "sources_workspace_created_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "deleted_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sources_workspace_status_idx": {
+ "name": "sources_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": {}
+ },
+ "sources_workspace_demo_key_idx": {
+ "name": "sources_workspace_demo_key_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "demo_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sources_workspace_document_idx": {
+ "name": "sources_workspace_document_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "knowhere_document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "knowhere_document_id IS NOT NULL AND deleted_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sources_workspace_id_workspaces_id_fk": {
+ "name": "sources_workspace_id_workspaces_id_fk",
+ "tableFrom": "sources",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspaces": {
+ "name": "workspaces",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "namespace": {
+ "name": "namespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workspaces_user_id_idx": {
+ "name": "workspaces_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "workspaces_user_id_unique": {
+ "name": "workspaces_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id"
+ ]
+ },
+ "workspaces_namespace_unique": {
+ "name": "workspaces_namespace_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "namespace"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/0016_snapshot.json b/drizzle/meta/0016_snapshot.json
new file mode 100644
index 00000000..77ef531c
--- /dev/null
+++ b/drizzle/meta/0016_snapshot.json
@@ -0,0 +1,1550 @@
+{
+ "id": "55d73dc7-9826-4cb5-9bb6-3506b8fa338a",
+ "prevId": "be8deb1f-3ec8-4304-85d0-41403629970c",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.chat_messages": {
+ "name": "chat_messages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "citations": {
+ "name": "citations",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "artifacts": {
+ "name": "artifacts",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "chat_messages_thread_created_idx": {
+ "name": "chat_messages_thread_created_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "chat_messages_thread_id_chat_threads_id_fk": {
+ "name": "chat_messages_thread_id_chat_threads_id_fk",
+ "tableFrom": "chat_messages",
+ "tableTo": "chat_threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.chat_threads": {
+ "name": "chat_threads",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "demo_key": {
+ "name": "demo_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "chat_threads_workspace_updated_idx": {
+ "name": "chat_threads_workspace_updated_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "deleted_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "chat_threads_workspace_demo_key_idx": {
+ "name": "chat_threads_workspace_demo_key_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "demo_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "chat_threads_workspace_id_workspaces_id_fk": {
+ "name": "chat_threads_workspace_id_workspaces_id_fk",
+ "tableFrom": "chat_threads",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.demo_source_visibilities": {
+ "name": "demo_source_visibilities",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "demo_source_id": {
+ "name": "demo_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hidden_at": {
+ "name": "hidden_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "demo_source_visibilities_workspace_source_idx": {
+ "name": "demo_source_visibilities_workspace_source_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "demo_source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "demo_source_visibilities_workspace_idx": {
+ "name": "demo_source_visibilities_workspace_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "demo_source_visibilities_workspace_id_workspaces_id_fk": {
+ "name": "demo_source_visibilities_workspace_id_workspaces_id_fk",
+ "tableFrom": "demo_source_visibilities",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.fluid_memory_items": {
+ "name": "fluid_memory_items",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "abstract_l0": {
+ "name": "abstract_l0",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "overview_l1": {
+ "name": "overview_l1",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_message_id": {
+ "name": "source_message_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confidence": {
+ "name": "confidence",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deactivation_reason": {
+ "name": "deactivation_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "fluid_memory_items_workspace_status_idx": {
+ "name": "fluid_memory_items_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": {}
+ },
+ "fluid_memory_items_workspace_kind_idx": {
+ "name": "fluid_memory_items_workspace_kind_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "fluid_memory_items_workspace_id_workspaces_id_fk": {
+ "name": "fluid_memory_items_workspace_id_workspaces_id_fk",
+ "tableFrom": "fluid_memory_items",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "fluid_memory_items_source_message_id_chat_messages_id_fk": {
+ "name": "fluid_memory_items_source_message_id_chat_messages_id_fk",
+ "tableFrom": "fluid_memory_items",
+ "tableTo": "chat_messages",
+ "columnsFrom": [
+ "source_message_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.fluid_memory_tokens": {
+ "name": "fluid_memory_tokens",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "item_id": {
+ "name": "item_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "frequency": {
+ "name": "frequency",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ }
+ },
+ "indexes": {
+ "fluid_memory_tokens_lookup_idx": {
+ "name": "fluid_memory_tokens_lookup_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "fluid_memory_tokens_item_idx": {
+ "name": "fluid_memory_tokens_item_idx",
+ "columns": [
+ {
+ "expression": "item_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "fluid_memory_tokens_workspace_id_workspaces_id_fk": {
+ "name": "fluid_memory_tokens_workspace_id_workspaces_id_fk",
+ "tableFrom": "fluid_memory_tokens",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "fluid_memory_tokens_item_id_fluid_memory_items_id_fk": {
+ "name": "fluid_memory_tokens_item_id_fluid_memory_items_id_fk",
+ "tableFrom": "fluid_memory_tokens",
+ "tableTo": "fluid_memory_items",
+ "columnsFrom": [
+ "item_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.fluid_observations": {
+ "name": "fluid_observations",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_message_id": {
+ "name": "source_message_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "signal": {
+ "name": "signal",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "evidence_quote": {
+ "name": "evidence_quote",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subject_hint": {
+ "name": "subject_hint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "referenced_document_ids": {
+ "name": "referenced_document_ids",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "confidence": {
+ "name": "confidence",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "fluid_observations_workspace_status_created_idx": {
+ "name": "fluid_observations_workspace_status_created_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "fluid_observations_workspace_id_workspaces_id_fk": {
+ "name": "fluid_observations_workspace_id_workspaces_id_fk",
+ "tableFrom": "fluid_observations",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "fluid_observations_source_message_id_chat_messages_id_fk": {
+ "name": "fluid_observations_source_message_id_chat_messages_id_fk",
+ "tableFrom": "fluid_observations",
+ "tableTo": "chat_messages",
+ "columnsFrom": [
+ "source_message_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.memory_diffs": {
+ "name": "memory_diffs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_message_id": {
+ "name": "source_message_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "operations": {
+ "name": "operations",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "memory_diffs_workspace_created_idx": {
+ "name": "memory_diffs_workspace_created_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "memory_diffs_workspace_id_workspaces_id_fk": {
+ "name": "memory_diffs_workspace_id_workspaces_id_fk",
+ "tableFrom": "memory_diffs",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "memory_diffs_source_message_id_chat_messages_id_fk": {
+ "name": "memory_diffs_source_message_id_chat_messages_id_fk",
+ "tableFrom": "memory_diffs",
+ "tableTo": "chat_messages",
+ "columnsFrom": [
+ "source_message_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.parsed_document_sync_leases": {
+ "name": "parsed_document_sync_leases",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "document_id": {
+ "name": "document_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revision_key": {
+ "name": "revision_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lease_token": {
+ "name": "lease_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "acquired_at": {
+ "name": "acquired_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "released_at": {
+ "name": "released_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "release_reason": {
+ "name": "release_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "parsed_document_sync_leases_token_idx": {
+ "name": "parsed_document_sync_leases_token_idx",
+ "columns": [
+ {
+ "expression": "lease_token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "parsed_document_sync_leases_active_idx": {
+ "name": "parsed_document_sync_leases_active_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "released_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "parsed_document_sync_leases_workspace_active_idx": {
+ "name": "parsed_document_sync_leases_workspace_active_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "released_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "parsed_document_sync_leases_document_active_idx": {
+ "name": "parsed_document_sync_leases_document_active_idx",
+ "columns": [
+ {
+ "expression": "document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "released_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "parsed_document_sync_leases_workspace_id_workspaces_id_fk": {
+ "name": "parsed_document_sync_leases_workspace_id_workspaces_id_fk",
+ "tableFrom": "parsed_document_sync_leases",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "parsed_document_sync_leases_source_id_sources_id_fk": {
+ "name": "parsed_document_sync_leases_source_id_sources_id_fk",
+ "tableFrom": "parsed_document_sync_leases",
+ "tableTo": "sources",
+ "columnsFrom": [
+ "source_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.retrieval_activations": {
+ "name": "retrieval_activations",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "unit_type": {
+ "name": "unit_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "unit_ref": {
+ "name": "unit_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "activation_count": {
+ "name": "activation_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "last_activated_at": {
+ "name": "last_activated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "retrieval_activations_unit_idx": {
+ "name": "retrieval_activations_unit_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "unit_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "unit_ref",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "retrieval_activations_workspace_id_workspaces_id_fk": {
+ "name": "retrieval_activations_workspace_id_workspaces_id_fk",
+ "tableFrom": "retrieval_activations",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.source_parse_results": {
+ "name": "source_parse_results",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "result_blob_url": {
+ "name": "result_blob_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "snapshot_manifest_url": {
+ "name": "snapshot_manifest_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "snapshot_manifest_key": {
+ "name": "snapshot_manifest_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revision_key": {
+ "name": "revision_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "asset_urls": {
+ "name": "asset_urls",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "source_parse_results_source_id_idx": {
+ "name": "source_parse_results_source_id_idx",
+ "columns": [
+ {
+ "expression": "source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "source_parse_results_source_id_sources_id_fk": {
+ "name": "source_parse_results_source_id_sources_id_fk",
+ "tableFrom": "source_parse_results",
+ "tableTo": "sources",
+ "columnsFrom": [
+ "source_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "source_parse_results_source_id_unique": {
+ "name": "source_parse_results_source_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "source_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sources": {
+ "name": "sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size_bytes": {
+ "name": "size_bytes",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "failure_reason": {
+ "name": "failure_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "failure_stage": {
+ "name": "failure_stage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "knowhere_job_id": {
+ "name": "knowhere_job_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "knowhere_document_id": {
+ "name": "knowhere_document_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "staged_blob_pathname": {
+ "name": "staged_blob_pathname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "staged_blob_url": {
+ "name": "staged_blob_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_blob_pathname": {
+ "name": "original_blob_pathname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_blob_url": {
+ "name": "original_blob_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "demo_key": {
+ "name": "demo_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "sources_workspace_created_idx": {
+ "name": "sources_workspace_created_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "deleted_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sources_workspace_status_idx": {
+ "name": "sources_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": {}
+ },
+ "sources_workspace_demo_key_idx": {
+ "name": "sources_workspace_demo_key_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "demo_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sources_workspace_document_idx": {
+ "name": "sources_workspace_document_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "knowhere_document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "knowhere_document_id IS NOT NULL AND deleted_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sources_workspace_id_workspaces_id_fk": {
+ "name": "sources_workspace_id_workspaces_id_fk",
+ "tableFrom": "sources",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspaces": {
+ "name": "workspaces",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "namespace": {
+ "name": "namespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workspaces_user_id_idx": {
+ "name": "workspaces_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "workspaces_user_id_unique": {
+ "name": "workspaces_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id"
+ ]
+ },
+ "workspaces_namespace_unique": {
+ "name": "workspaces_namespace_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "namespace"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/0017_snapshot.json b/drizzle/meta/0017_snapshot.json
new file mode 100644
index 00000000..52d88234
--- /dev/null
+++ b/drizzle/meta/0017_snapshot.json
@@ -0,0 +1,932 @@
+{
+ "id": "d4de5ce8-e16b-4dc2-a9ad-2b76bbca8150",
+ "prevId": "55d73dc7-9826-4cb5-9bb6-3506b8fa338a",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.chat_messages": {
+ "name": "chat_messages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "citations": {
+ "name": "citations",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "artifacts": {
+ "name": "artifacts",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "chat_messages_thread_created_idx": {
+ "name": "chat_messages_thread_created_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "chat_messages_thread_id_chat_threads_id_fk": {
+ "name": "chat_messages_thread_id_chat_threads_id_fk",
+ "tableFrom": "chat_messages",
+ "tableTo": "chat_threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.chat_threads": {
+ "name": "chat_threads",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "demo_key": {
+ "name": "demo_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "chat_threads_workspace_updated_idx": {
+ "name": "chat_threads_workspace_updated_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "deleted_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "chat_threads_workspace_demo_key_idx": {
+ "name": "chat_threads_workspace_demo_key_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "demo_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "chat_threads_workspace_id_workspaces_id_fk": {
+ "name": "chat_threads_workspace_id_workspaces_id_fk",
+ "tableFrom": "chat_threads",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.demo_source_visibilities": {
+ "name": "demo_source_visibilities",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "demo_source_id": {
+ "name": "demo_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hidden_at": {
+ "name": "hidden_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "demo_source_visibilities_workspace_source_idx": {
+ "name": "demo_source_visibilities_workspace_source_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "demo_source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "demo_source_visibilities_workspace_idx": {
+ "name": "demo_source_visibilities_workspace_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "demo_source_visibilities_workspace_id_workspaces_id_fk": {
+ "name": "demo_source_visibilities_workspace_id_workspaces_id_fk",
+ "tableFrom": "demo_source_visibilities",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.parsed_document_sync_leases": {
+ "name": "parsed_document_sync_leases",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "document_id": {
+ "name": "document_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revision_key": {
+ "name": "revision_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lease_token": {
+ "name": "lease_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "acquired_at": {
+ "name": "acquired_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "released_at": {
+ "name": "released_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "release_reason": {
+ "name": "release_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "parsed_document_sync_leases_token_idx": {
+ "name": "parsed_document_sync_leases_token_idx",
+ "columns": [
+ {
+ "expression": "lease_token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "parsed_document_sync_leases_active_idx": {
+ "name": "parsed_document_sync_leases_active_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "released_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "parsed_document_sync_leases_workspace_active_idx": {
+ "name": "parsed_document_sync_leases_workspace_active_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "released_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "parsed_document_sync_leases_document_active_idx": {
+ "name": "parsed_document_sync_leases_document_active_idx",
+ "columns": [
+ {
+ "expression": "document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "released_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "parsed_document_sync_leases_workspace_id_workspaces_id_fk": {
+ "name": "parsed_document_sync_leases_workspace_id_workspaces_id_fk",
+ "tableFrom": "parsed_document_sync_leases",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "parsed_document_sync_leases_source_id_sources_id_fk": {
+ "name": "parsed_document_sync_leases_source_id_sources_id_fk",
+ "tableFrom": "parsed_document_sync_leases",
+ "tableTo": "sources",
+ "columnsFrom": [
+ "source_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.source_parse_results": {
+ "name": "source_parse_results",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "result_blob_url": {
+ "name": "result_blob_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "snapshot_manifest_url": {
+ "name": "snapshot_manifest_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "snapshot_manifest_key": {
+ "name": "snapshot_manifest_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revision_key": {
+ "name": "revision_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "asset_urls": {
+ "name": "asset_urls",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "source_parse_results_source_id_idx": {
+ "name": "source_parse_results_source_id_idx",
+ "columns": [
+ {
+ "expression": "source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "source_parse_results_source_id_sources_id_fk": {
+ "name": "source_parse_results_source_id_sources_id_fk",
+ "tableFrom": "source_parse_results",
+ "tableTo": "sources",
+ "columnsFrom": [
+ "source_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "source_parse_results_source_id_unique": {
+ "name": "source_parse_results_source_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "source_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sources": {
+ "name": "sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size_bytes": {
+ "name": "size_bytes",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "failure_reason": {
+ "name": "failure_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "failure_stage": {
+ "name": "failure_stage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "knowhere_job_id": {
+ "name": "knowhere_job_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "knowhere_document_id": {
+ "name": "knowhere_document_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "staged_blob_pathname": {
+ "name": "staged_blob_pathname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "staged_blob_url": {
+ "name": "staged_blob_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_blob_pathname": {
+ "name": "original_blob_pathname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_blob_url": {
+ "name": "original_blob_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "demo_key": {
+ "name": "demo_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "sources_workspace_created_idx": {
+ "name": "sources_workspace_created_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "deleted_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sources_workspace_status_idx": {
+ "name": "sources_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": {}
+ },
+ "sources_workspace_demo_key_idx": {
+ "name": "sources_workspace_demo_key_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "demo_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sources_workspace_document_idx": {
+ "name": "sources_workspace_document_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "knowhere_document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "knowhere_document_id IS NOT NULL AND deleted_at IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sources_workspace_id_workspaces_id_fk": {
+ "name": "sources_workspace_id_workspaces_id_fk",
+ "tableFrom": "sources",
+ "tableTo": "workspaces",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspaces": {
+ "name": "workspaces",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "namespace": {
+ "name": "namespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workspaces_user_id_idx": {
+ "name": "workspaces_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "workspaces_user_id_unique": {
+ "name": "workspaces_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id"
+ ]
+ },
+ "workspaces_namespace_unique": {
+ "name": "workspaces_namespace_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "namespace"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index b9be4f54..4ee28af6 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -106,6 +106,27 @@
"when": 1788252035435,
"tag": "0014_messy_apocalypse",
"breakpoints": true
+ },
+ {
+ "idx": 15,
+ "version": "7",
+ "when": 1788418416499,
+ "tag": "0015_bumpy_vulcan",
+ "breakpoints": true
+ },
+ {
+ "idx": 16,
+ "version": "7",
+ "when": 1788952172906,
+ "tag": "0016_wealthy_matthew_murdock",
+ "breakpoints": true
+ },
+ {
+ "idx": 17,
+ "version": "7",
+ "when": 1789017481605,
+ "tag": "0017_overjoyed_shooting_star",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/next.config.ts b/next.config.ts
index b73f5201..cd1fac0a 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -10,6 +10,7 @@ const nextConfig: NextConfig = {
"@ontos-ai/knowhere-sdk",
"@napi-rs/canvas",
"piscina",
+ "@modelcontextprotocol/sdk",
],
allowedDevOrigins: [
"127.0.0.1",
diff --git a/package.json b/package.json
index f3778893..4ab25322 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"@ai-sdk/react": "^3.0.177",
"@antv/chart-visualization-skills": "0.1.3",
"@effect/platform": "^0.96.1",
+ "@modelcontextprotocol/sdk": "^1.30.0",
"@napi-rs/canvas": "^1.0.2",
"@neondatabase/serverless": "^1.1.0",
"@ontos-ai/knowhere-sdk": "^2.2.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8847510a..d4db84c2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,6 +17,9 @@ importers:
'@effect/platform':
specifier: ^0.96.1
version: 0.96.1(effect@3.21.2)
+ '@modelcontextprotocol/sdk':
+ specifier: ^1.30.0
+ version: 1.30.0(supports-color@7.2.0)(zod@4.4.3)
'@napi-rs/canvas':
specifier: ^1.0.2
version: 1.0.2
@@ -25,7 +28,7 @@ importers:
version: 1.1.0
'@ontos-ai/knowhere-sdk':
specifier: ^2.2.0
- version: 2.2.0
+ version: 2.2.0(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)
'@radix-ui/react-alert-dialog':
specifier: ^1.1.15
version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -97,7 +100,7 @@ importers:
version: 1.12.0
next:
specifier: 16.2.4
- version: 16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ version: 16.2.4(@babel/core@7.29.0(supports-color@7.2.0))(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -121,13 +124,13 @@ importers:
version: 19.2.4(react@19.2.4)
react-markdown:
specifier: ^10.1.0
- version: 10.1.0(@types/react@19.2.14)(react@19.2.4)
+ version: 10.1.0(@types/react@19.2.14)(react@19.2.4)(supports-color@7.2.0)
react-pdf:
specifier: ^10.4.1
version: 10.4.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
remark-gfm:
specifier: ^4.0.1
- version: 4.0.1
+ version: 4.0.1(supports-color@7.2.0)
server-only:
specifier: ^0.0.1
version: 0.0.1
@@ -182,16 +185,16 @@ importers:
version: 0.31.10
eslint:
specifier: ^9
- version: 9.39.4(jiti@2.7.0)
+ version: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
eslint-config-next:
specifier: 16.2.4
- version: 16.2.4(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)
+ version: 16.2.4(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)
jsdom:
specifier: ^29.1.1
version: 29.1.1(@noble/hashes@1.8.0)
shadcn:
specifier: ^4.7.0
- version: 4.7.0(@types/node@20.19.39)(typescript@6.0.3)
+ version: 4.7.0(@types/node@20.19.39)(supports-color@7.2.0)(typescript@6.0.3)
tailwindcss:
specifier: ^4
version: 4.2.4
@@ -455,11 +458,11 @@ packages:
'@esbuild-kit/core-utils@3.3.2':
resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
- deprecated: 'Merged into tsx: https://tsx.is'
+ deprecated: 'Merged into tsx: https://tsx.hirok.io'
'@esbuild-kit/esm-loader@2.6.5':
resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==}
- deprecated: 'Merged into tsx: https://tsx.is'
+ deprecated: 'Merged into tsx: https://tsx.hirok.io'
'@esbuild/aix-ppc64@0.25.12':
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
@@ -1197,8 +1200,8 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
- '@modelcontextprotocol/sdk@1.29.0':
- resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
+ '@modelcontextprotocol/sdk@1.30.0':
+ resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==}
engines: {node: '>=18'}
peerDependencies:
'@cfworker/json-schema': ^4.1.1
@@ -2594,6 +2597,7 @@ packages:
'@xmldom/xmldom@0.8.13':
resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==}
engines: {node: '>=10.0.0'}
+ deprecated: this version has critical issues, please update to the latest version
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
@@ -2948,6 +2952,7 @@ packages:
crypto-js@4.2.0:
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
+ deprecated: Active development of CryptoJS has been discontinued. This library is no longer maintained.
css-tree@3.2.1:
resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
@@ -3394,6 +3399,7 @@ packages:
eslint@9.39.4:
resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
hasBin: true
peerDependencies:
jiti: '*'
@@ -5733,20 +5739,20 @@ snapshots:
'@babel/compat-data@7.29.3': {}
- '@babel/core@7.29.0':
+ '@babel/core@7.29.0(supports-color@7.2.0)':
dependencies:
'@babel/code-frame': 7.29.0
'@babel/generator': 7.29.1
'@babel/helper-compilation-targets': 7.28.6
- '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)
'@babel/helpers': 7.29.2
'@babel/parser': 7.29.3
'@babel/template': 7.28.6
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@7.2.0)
'@babel/types': 7.29.0
'@jridgewell/remapping': 2.3.5
convert-source-map: 2.0.0
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -5773,41 +5779,41 @@ snapshots:
lru-cache: 5.1.1
semver: 6.3.1
- '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.29.0)':
+ '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@7.2.0)
'@babel/helper-annotate-as-pure': 7.27.3
- '@babel/helper-member-expression-to-functions': 7.28.5
+ '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@7.2.0)
'@babel/helper-optimise-call-expression': 7.27.1
- '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0)
- '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
- '@babel/traverse': 7.29.0
+ '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@7.2.0)
+ '@babel/traverse': 7.29.0(supports-color@7.2.0)
semver: 6.3.1
transitivePeerDependencies:
- supports-color
'@babel/helper-globals@7.28.0': {}
- '@babel/helper-member-expression-to-functions@7.28.5':
+ '@babel/helper-member-expression-to-functions@7.28.5(supports-color@7.2.0)':
dependencies:
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@7.2.0)
'@babel/types': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/helper-module-imports@7.28.6':
+ '@babel/helper-module-imports@7.28.6(supports-color@7.2.0)':
dependencies:
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@7.2.0)
'@babel/types': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)':
+ '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-module-imports': 7.28.6
+ '@babel/core': 7.29.0(supports-color@7.2.0)
+ '@babel/helper-module-imports': 7.28.6(supports-color@7.2.0)
'@babel/helper-validator-identifier': 7.28.5
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@7.2.0)
transitivePeerDependencies:
- supports-color
@@ -5817,18 +5823,18 @@ snapshots:
'@babel/helper-plugin-utils@7.28.6': {}
- '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)':
+ '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-member-expression-to-functions': 7.28.5
+ '@babel/core': 7.29.0(supports-color@7.2.0)
+ '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@7.2.0)
'@babel/helper-optimise-call-expression': 7.27.1
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@7.2.0)
transitivePeerDependencies:
- supports-color
- '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+ '@babel/helper-skip-transparent-expression-wrappers@7.27.1(supports-color@7.2.0)':
dependencies:
- '@babel/traverse': 7.29.0
+ '@babel/traverse': 7.29.0(supports-color@7.2.0)
'@babel/types': 7.29.0
transitivePeerDependencies:
- supports-color
@@ -5848,43 +5854,43 @@ snapshots:
dependencies:
'@babel/types': 7.29.0
- '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0(supports-color@7.2.0))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@7.2.0)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0(supports-color@7.2.0))':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@7.2.0)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)':
dependencies:
- '@babel/core': 7.29.0
- '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/core': 7.29.0(supports-color@7.2.0)
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)
'@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)':
+ '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@7.2.0)
'@babel/helper-annotate-as-pure': 7.27.3
- '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0)
+ '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
- '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@7.2.0)
+ '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0(supports-color@7.2.0))
transitivePeerDependencies:
- supports-color
- '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)':
+ '@babel/preset-typescript@7.28.5(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)':
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@7.2.0)
'@babel/helper-plugin-utils': 7.28.6
'@babel/helper-validator-option': 7.27.1
- '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0)
- '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0(supports-color@7.2.0))
+ '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)
+ '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)
transitivePeerDependencies:
- supports-color
@@ -5896,7 +5902,7 @@ snapshots:
'@babel/parser': 7.29.3
'@babel/types': 7.29.0
- '@babel/traverse@7.29.0':
+ '@babel/traverse@7.29.0(supports-color@7.2.0)':
dependencies:
'@babel/code-frame': 7.29.0
'@babel/generator': 7.29.1
@@ -5904,7 +5910,7 @@ snapshots:
'@babel/parser': 7.29.3
'@babel/template': 7.28.6
'@babel/types': 7.29.0
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
transitivePeerDependencies:
- supports-color
@@ -6217,17 +6223,17 @@ snapshots:
'@esbuild/win32-x64@0.27.7':
optional: true
- '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.7.0))':
+ '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))':
dependencies:
- eslint: 9.39.4(jiti@2.7.0)
+ eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
- '@eslint/config-array@0.21.2':
+ '@eslint/config-array@0.21.2(supports-color@7.2.0)':
dependencies:
'@eslint/object-schema': 2.1.7
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
minimatch: 3.1.5
transitivePeerDependencies:
- supports-color
@@ -6240,10 +6246,10 @@ snapshots:
dependencies:
'@types/json-schema': 7.0.15
- '@eslint/eslintrc@3.3.5':
+ '@eslint/eslintrc@3.3.5(supports-color@7.2.0)':
dependencies:
ajv: 6.15.0
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
espree: 10.4.0
globals: 14.0.0
ignore: 5.3.2
@@ -6447,7 +6453,7 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
- '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)':
+ '@modelcontextprotocol/sdk@1.30.0(supports-color@7.2.0)(zod@3.25.76)':
dependencies:
'@hono/node-server': 1.19.14(hono@4.12.17)
ajv: 8.20.0
@@ -6457,8 +6463,8 @@ snapshots:
cross-spawn: 7.0.6
eventsource: 3.0.7
eventsource-parser: 3.0.8
- express: 5.2.1
- express-rate-limit: 8.5.0(express@5.2.1)
+ express: 5.2.1(supports-color@7.2.0)
+ express-rate-limit: 8.5.0(express@5.2.1(supports-color@7.2.0))
hono: 4.12.17
jose: 6.2.3
json-schema-typed: 8.0.2
@@ -6469,6 +6475,28 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@modelcontextprotocol/sdk@1.30.0(supports-color@7.2.0)(zod@4.4.3)':
+ dependencies:
+ '@hono/node-server': 1.19.14(hono@4.12.17)
+ ajv: 8.20.0
+ ajv-formats: 3.0.1(ajv@8.20.0)
+ content-type: 1.0.5
+ cors: 2.8.6
+ cross-spawn: 7.0.6
+ eventsource: 3.0.7
+ eventsource-parser: 3.0.8
+ express: 5.2.1(supports-color@7.2.0)
+ express-rate-limit: 8.5.0(express@5.2.1(supports-color@7.2.0))
+ hono: 4.12.17
+ jose: 6.2.3
+ json-schema-typed: 8.0.2
+ pkce-challenge: 5.0.1
+ raw-body: 3.0.2
+ zod: 4.4.3
+ zod-to-json-schema: 3.25.2(zod@4.4.3)
+ transitivePeerDependencies:
+ - supports-color
+
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
optional: true
@@ -6731,9 +6759,9 @@ snapshots:
'@nolyfill/is-core-module@1.0.39': {}
- '@ontos-ai/knowhere-sdk@2.2.0':
+ '@ontos-ai/knowhere-sdk@2.2.0(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)':
dependencies:
- axios: 1.18.1
+ axios: 1.18.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)
jszip: 3.10.1
transitivePeerDependencies:
- debug
@@ -7400,15 +7428,15 @@ snapshots:
'@types/validate-npm-package-name@4.0.2': {}
- '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)':
+ '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)
'@typescript-eslint/scope-manager': 8.59.2
- '@typescript-eslint/type-utils': 8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)
- '@typescript-eslint/utils': 8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)
+ '@typescript-eslint/type-utils': 8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.59.2
- eslint: 9.39.4(jiti@2.7.0)
+ eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
ignore: 7.0.5
natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@6.0.3)
@@ -7416,23 +7444,23 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)':
+ '@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.59.2
'@typescript-eslint/types': 8.59.2
- '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3)
+ '@typescript-eslint/typescript-estree': 8.59.2(supports-color@7.2.0)(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.59.2
- debug: 4.4.3
- eslint: 9.39.4(jiti@2.7.0)
+ debug: 4.4.3(supports-color@7.2.0)
+ eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/project-service@8.59.2(typescript@6.0.3)':
+ '@typescript-eslint/project-service@8.59.2(supports-color@7.2.0)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.59.2(typescript@6.0.3)
'@typescript-eslint/types': 8.59.2
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -7446,13 +7474,13 @@ snapshots:
dependencies:
typescript: 6.0.3
- '@typescript-eslint/type-utils@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)':
+ '@typescript-eslint/type-utils@8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/types': 8.59.2
- '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3)
- '@typescript-eslint/utils': 8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)
- debug: 4.4.3
- eslint: 9.39.4(jiti@2.7.0)
+ '@typescript-eslint/typescript-estree': 8.59.2(supports-color@7.2.0)(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)
+ debug: 4.4.3(supports-color@7.2.0)
+ eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 6.0.3
transitivePeerDependencies:
@@ -7460,13 +7488,13 @@ snapshots:
'@typescript-eslint/types@8.59.2': {}
- '@typescript-eslint/typescript-estree@8.59.2(typescript@6.0.3)':
+ '@typescript-eslint/typescript-estree@8.59.2(supports-color@7.2.0)(typescript@6.0.3)':
dependencies:
- '@typescript-eslint/project-service': 8.59.2(typescript@6.0.3)
+ '@typescript-eslint/project-service': 8.59.2(supports-color@7.2.0)(typescript@6.0.3)
'@typescript-eslint/tsconfig-utils': 8.59.2(typescript@6.0.3)
'@typescript-eslint/types': 8.59.2
'@typescript-eslint/visitor-keys': 8.59.2
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
minimatch: 10.2.5
semver: 7.7.4
tinyglobby: 0.2.16
@@ -7475,13 +7503,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)':
+ '@typescript-eslint/utils@8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0))
+ '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))
'@typescript-eslint/scope-manager': 8.59.2
'@typescript-eslint/types': 8.59.2
- '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3)
- eslint: 9.39.4(jiti@2.7.0)
+ '@typescript-eslint/typescript-estree': 8.59.2(supports-color@7.2.0)(typescript@6.0.3)
+ eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -7635,9 +7663,9 @@ snapshots:
acorn@8.16.0: {}
- agent-base@6.0.2:
+ agent-base@6.0.2(supports-color@7.2.0):
dependencies:
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
transitivePeerDependencies:
- supports-color
@@ -7784,11 +7812,11 @@ snapshots:
axe-core@4.11.4: {}
- axios@1.18.1:
+ axios@1.18.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0):
dependencies:
- follow-redirects: 1.16.0
+ follow-redirects: 1.16.0(debug@4.4.3(supports-color@7.2.0))
form-data: 4.0.6
- https-proxy-agent: 5.0.1
+ https-proxy-agent: 5.0.1(supports-color@7.2.0)
proxy-from-env: 2.1.0
transitivePeerDependencies:
- debug
@@ -7816,11 +7844,11 @@ snapshots:
bluebird@3.4.7: {}
- body-parser@2.2.2:
+ body-parser@2.2.2(supports-color@7.2.0):
dependencies:
bytes: 3.1.2
content-type: 1.0.5
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
http-errors: 2.0.1
iconv-lite: 0.7.2
on-finished: 2.4.1
@@ -8021,13 +8049,17 @@ snapshots:
es-errors: 1.3.0
is-data-view: 1.0.2
- debug@3.2.7:
+ debug@3.2.7(supports-color@7.2.0):
dependencies:
ms: 2.1.3
+ optionalDependencies:
+ supports-color: 7.2.0
- debug@4.4.3:
+ debug@4.4.3(supports-color@7.2.0):
dependencies:
ms: 2.1.3
+ optionalDependencies:
+ supports-color: 7.2.0
decimal.js@10.6.0: {}
@@ -8350,18 +8382,18 @@ snapshots:
escape-string-regexp@5.0.0: {}
- eslint-config-next@16.2.4(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3):
+ eslint-config-next@16.2.4(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3):
dependencies:
'@next/eslint-plugin-next': 16.2.4
- eslint: 9.39.4(jiti@2.7.0)
- eslint-import-resolver-node: 0.3.10
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0))
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
- eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0))
- eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0))
- eslint-plugin-react-hooks: 7.1.1(eslint@9.39.4(jiti@2.7.0))
+ eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
+ eslint-import-resolver-node: 0.3.10(supports-color@7.2.0)
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
+ eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))
+ eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))
+ eslint-plugin-react-hooks: 7.1.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
globals: 16.4.0
- typescript-eslint: 8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)
+ typescript-eslint: 8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)
optionalDependencies:
typescript: 6.0.3
transitivePeerDependencies:
@@ -8370,52 +8402,52 @@ snapshots:
- eslint-plugin-import-x
- supports-color
- eslint-import-resolver-node@0.3.10:
+ eslint-import-resolver-node@0.3.10(supports-color@7.2.0):
dependencies:
- debug: 3.2.7
+ debug: 3.2.7(supports-color@7.2.0)
is-core-module: 2.16.2
resolve: 2.0.0-next.6
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)):
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0):
dependencies:
'@nolyfill/is-core-module': 1.0.39
- debug: 4.4.3
- eslint: 9.39.4(jiti@2.7.0)
+ debug: 4.4.3(supports-color@7.2.0)
+ eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
get-tsconfig: 4.14.0
is-bun-module: 2.0.0
stable-hash: 0.0.5
tinyglobby: 0.2.16
unrs-resolver: 1.11.1
optionalDependencies:
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)):
+ eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0):
dependencies:
- debug: 3.2.7
+ debug: 3.2.7(supports-color@7.2.0)
optionalDependencies:
- '@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)
- eslint: 9.39.4(jiti@2.7.0)
- eslint-import-resolver-node: 0.3.10
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0))
+ '@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)
+ eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
+ eslint-import-resolver-node: 0.3.10(supports-color@7.2.0)
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)):
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
array.prototype.findlastindex: 1.2.6
array.prototype.flat: 1.3.3
array.prototype.flatmap: 1.3.3
- debug: 3.2.7
+ debug: 3.2.7(supports-color@7.2.0)
doctrine: 2.1.0
- eslint: 9.39.4(jiti@2.7.0)
- eslint-import-resolver-node: 0.3.10
- eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
+ eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
+ eslint-import-resolver-node: 0.3.10(supports-color@7.2.0)
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
hasown: 2.0.3
is-core-module: 2.16.2
is-glob: 4.0.3
@@ -8427,13 +8459,13 @@ snapshots:
string.prototype.trimend: 1.0.9
tsconfig-paths: 3.15.0
optionalDependencies:
- '@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.7.0)):
+ eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0)):
dependencies:
aria-query: 5.3.2
array-includes: 3.1.9
@@ -8443,7 +8475,7 @@ snapshots:
axobject-query: 4.1.0
damerau-levenshtein: 1.0.8
emoji-regex: 9.2.2
- eslint: 9.39.4(jiti@2.7.0)
+ eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
hasown: 2.0.3
jsx-ast-utils: 3.3.5
language-tags: 1.0.9
@@ -8452,18 +8484,18 @@ snapshots:
safe-regex-test: 1.1.0
string.prototype.includes: 2.0.1
- eslint-plugin-react-hooks@7.1.1(eslint@9.39.4(jiti@2.7.0)):
+ eslint-plugin-react-hooks@7.1.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0):
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@7.2.0)
'@babel/parser': 7.29.3
- eslint: 9.39.4(jiti@2.7.0)
+ eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
hermes-parser: 0.25.1
zod: 4.4.3
zod-validation-error: 4.0.2(zod@4.4.3)
transitivePeerDependencies:
- supports-color
- eslint-plugin-react@7.37.5(eslint@9.39.4(jiti@2.7.0)):
+ eslint-plugin-react@7.37.5(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0)):
dependencies:
array-includes: 3.1.9
array.prototype.findlast: 1.2.5
@@ -8471,7 +8503,7 @@ snapshots:
array.prototype.tosorted: 1.1.4
doctrine: 2.1.0
es-iterator-helpers: 1.3.2
- eslint: 9.39.4(jiti@2.7.0)
+ eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
estraverse: 5.3.0
hasown: 2.0.3
jsx-ast-utils: 3.3.5
@@ -8496,14 +8528,14 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
- eslint@9.39.4(jiti@2.7.0):
+ eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0):
dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0))
+ '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))
'@eslint-community/regexpp': 4.12.2
- '@eslint/config-array': 0.21.2
+ '@eslint/config-array': 0.21.2(supports-color@7.2.0)
'@eslint/config-helpers': 0.4.2
'@eslint/core': 0.17.0
- '@eslint/eslintrc': 3.3.5
+ '@eslint/eslintrc': 3.3.5(supports-color@7.2.0)
'@eslint/js': 9.39.4
'@eslint/plugin-kit': 0.4.1
'@humanfs/node': 0.16.8
@@ -8513,7 +8545,7 @@ snapshots:
ajv: 6.15.0
chalk: 4.1.2
cross-spawn: 7.0.6
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
escape-string-regexp: 4.0.0
eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1
@@ -8600,25 +8632,25 @@ snapshots:
expect-type@1.3.0: {}
- express-rate-limit@8.5.0(express@5.2.1):
+ express-rate-limit@8.5.0(express@5.2.1(supports-color@7.2.0)):
dependencies:
- express: 5.2.1
+ express: 5.2.1(supports-color@7.2.0)
ip-address: 10.1.0
- express@5.2.1:
+ express@5.2.1(supports-color@7.2.0):
dependencies:
accepts: 2.0.0
- body-parser: 2.2.2
+ body-parser: 2.2.2(supports-color@7.2.0)
content-disposition: 1.1.0
content-type: 1.0.5
cookie: 0.7.2
cookie-signature: 1.2.2
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
depd: 2.0.0
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
- finalhandler: 2.1.1
+ finalhandler: 2.1.1(supports-color@7.2.0)
fresh: 2.0.0
http-errors: 2.0.1
merge-descriptors: 2.0.0
@@ -8629,9 +8661,9 @@ snapshots:
proxy-addr: 2.0.7
qs: 6.15.1
range-parser: 1.2.1
- router: 2.2.0
- send: 1.2.1
- serve-static: 2.2.1
+ router: 2.2.0(supports-color@7.2.0)
+ send: 1.2.1(supports-color@7.2.0)
+ serve-static: 2.2.1(supports-color@7.2.0)
statuses: 2.0.2
type-is: 2.0.1
vary: 1.1.2
@@ -8709,9 +8741,9 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
- finalhandler@2.1.1:
+ finalhandler@2.1.1(supports-color@7.2.0):
dependencies:
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
encodeurl: 2.0.0
escape-html: 1.0.3
on-finished: 2.4.1
@@ -8734,7 +8766,9 @@ snapshots:
flatted@3.4.2: {}
- follow-redirects@1.16.0: {}
+ follow-redirects@1.16.0(debug@4.4.3(supports-color@7.2.0)):
+ optionalDependencies:
+ debug: 4.4.3(supports-color@7.2.0)
for-each@0.3.5:
dependencies:
@@ -8886,7 +8920,7 @@ snapshots:
dependencies:
function-bind: 1.1.2
- hast-util-to-jsx-runtime@2.3.6:
+ hast-util-to-jsx-runtime@2.3.6(supports-color@7.2.0):
dependencies:
'@types/estree': 1.0.8
'@types/hast': 3.0.4
@@ -8895,9 +8929,9 @@ snapshots:
devlop: 1.1.0
estree-util-is-identifier-name: 3.0.0
hast-util-whitespace: 3.0.0
- mdast-util-mdx-expression: 2.0.1
- mdast-util-mdx-jsx: 3.2.0
- mdast-util-mdxjs-esm: 2.0.1
+ mdast-util-mdx-expression: 2.0.1(supports-color@7.2.0)
+ mdast-util-mdx-jsx: 3.2.0(supports-color@7.2.0)
+ mdast-util-mdxjs-esm: 2.0.1(supports-color@7.2.0)
property-information: 7.1.0
space-separated-tokens: 2.0.2
style-to-js: 1.1.21
@@ -8939,17 +8973,17 @@ snapshots:
statuses: 2.0.2
toidentifier: 1.0.1
- https-proxy-agent@5.0.1:
+ https-proxy-agent@5.0.1(supports-color@7.2.0):
dependencies:
- agent-base: 6.0.2
- debug: 4.4.3
+ agent-base: 6.0.2(supports-color@7.2.0)
+ debug: 4.4.3(supports-color@7.2.0)
transitivePeerDependencies:
- supports-color
- https-proxy-agent@7.0.6:
+ https-proxy-agent@7.0.6(supports-color@7.2.0):
dependencies:
agent-base: 7.1.4
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
transitivePeerDependencies:
- supports-color
@@ -9398,14 +9432,14 @@ snapshots:
unist-util-is: 6.0.1
unist-util-visit-parents: 6.0.2
- mdast-util-from-markdown@2.0.3:
+ mdast-util-from-markdown@2.0.3(supports-color@7.2.0):
dependencies:
'@types/mdast': 4.0.4
'@types/unist': 3.0.3
decode-named-character-reference: 1.3.0
devlop: 1.1.0
mdast-util-to-string: 4.0.0
- micromark: 4.0.2
+ micromark: 4.0.2(supports-color@7.2.0)
micromark-util-decode-numeric-character-reference: 2.0.2
micromark-util-decode-string: 2.0.1
micromark-util-normalize-identifier: 2.0.1
@@ -9423,67 +9457,67 @@ snapshots:
mdast-util-find-and-replace: 3.0.2
micromark-util-character: 2.1.1
- mdast-util-gfm-footnote@2.1.0:
+ mdast-util-gfm-footnote@2.1.0(supports-color@7.2.0):
dependencies:
'@types/mdast': 4.0.4
devlop: 1.1.0
- mdast-util-from-markdown: 2.0.3
+ mdast-util-from-markdown: 2.0.3(supports-color@7.2.0)
mdast-util-to-markdown: 2.1.2
micromark-util-normalize-identifier: 2.0.1
transitivePeerDependencies:
- supports-color
- mdast-util-gfm-strikethrough@2.0.0:
+ mdast-util-gfm-strikethrough@2.0.0(supports-color@7.2.0):
dependencies:
'@types/mdast': 4.0.4
- mdast-util-from-markdown: 2.0.3
+ mdast-util-from-markdown: 2.0.3(supports-color@7.2.0)
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
- mdast-util-gfm-table@2.0.0:
+ mdast-util-gfm-table@2.0.0(supports-color@7.2.0):
dependencies:
'@types/mdast': 4.0.4
devlop: 1.1.0
markdown-table: 3.0.4
- mdast-util-from-markdown: 2.0.3
+ mdast-util-from-markdown: 2.0.3(supports-color@7.2.0)
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
- mdast-util-gfm-task-list-item@2.0.0:
+ mdast-util-gfm-task-list-item@2.0.0(supports-color@7.2.0):
dependencies:
'@types/mdast': 4.0.4
devlop: 1.1.0
- mdast-util-from-markdown: 2.0.3
+ mdast-util-from-markdown: 2.0.3(supports-color@7.2.0)
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
- mdast-util-gfm@3.1.0:
+ mdast-util-gfm@3.1.0(supports-color@7.2.0):
dependencies:
- mdast-util-from-markdown: 2.0.3
+ mdast-util-from-markdown: 2.0.3(supports-color@7.2.0)
mdast-util-gfm-autolink-literal: 2.0.1
- mdast-util-gfm-footnote: 2.1.0
- mdast-util-gfm-strikethrough: 2.0.0
- mdast-util-gfm-table: 2.0.0
- mdast-util-gfm-task-list-item: 2.0.0
+ mdast-util-gfm-footnote: 2.1.0(supports-color@7.2.0)
+ mdast-util-gfm-strikethrough: 2.0.0(supports-color@7.2.0)
+ mdast-util-gfm-table: 2.0.0(supports-color@7.2.0)
+ mdast-util-gfm-task-list-item: 2.0.0(supports-color@7.2.0)
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
- mdast-util-mdx-expression@2.0.1:
+ mdast-util-mdx-expression@2.0.1(supports-color@7.2.0):
dependencies:
'@types/estree-jsx': 1.0.5
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
devlop: 1.1.0
- mdast-util-from-markdown: 2.0.3
+ mdast-util-from-markdown: 2.0.3(supports-color@7.2.0)
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
- mdast-util-mdx-jsx@3.2.0:
+ mdast-util-mdx-jsx@3.2.0(supports-color@7.2.0):
dependencies:
'@types/estree-jsx': 1.0.5
'@types/hast': 3.0.4
@@ -9491,7 +9525,7 @@ snapshots:
'@types/unist': 3.0.3
ccount: 2.0.1
devlop: 1.1.0
- mdast-util-from-markdown: 2.0.3
+ mdast-util-from-markdown: 2.0.3(supports-color@7.2.0)
mdast-util-to-markdown: 2.1.2
parse-entities: 4.0.2
stringify-entities: 4.0.4
@@ -9500,13 +9534,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- mdast-util-mdxjs-esm@2.0.1:
+ mdast-util-mdxjs-esm@2.0.1(supports-color@7.2.0):
dependencies:
'@types/estree-jsx': 1.0.5
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
devlop: 1.1.0
- mdast-util-from-markdown: 2.0.3
+ mdast-util-from-markdown: 2.0.3(supports-color@7.2.0)
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
@@ -9727,10 +9761,10 @@ snapshots:
micromark-util-types@2.0.2: {}
- micromark@4.0.2:
+ micromark@4.0.2(supports-color@7.2.0):
dependencies:
'@types/debug': 4.1.13
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
decode-named-character-reference: 1.3.0
devlop: 1.1.0
micromark-core-commonmark: 2.0.3
@@ -9842,7 +9876,7 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
- next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
+ next@16.2.4(@babel/core@7.29.0(supports-color@7.2.0))(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies:
'@next/env': 16.2.4
'@swc/helpers': 0.5.15
@@ -9851,7 +9885,7 @@ snapshots:
postcss: 8.4.31
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
- styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.4)
+ styled-jsx: 5.1.6(@babel/core@7.29.0(supports-color@7.2.0))(react@19.2.4)
optionalDependencies:
'@next/swc-darwin-arm64': 16.2.4
'@next/swc-darwin-x64': 16.2.4
@@ -10183,17 +10217,17 @@ snapshots:
react-is@17.0.2: {}
- react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.4):
+ react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.4)(supports-color@7.2.0):
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
'@types/react': 19.2.14
devlop: 1.1.0
- hast-util-to-jsx-runtime: 2.3.6
+ hast-util-to-jsx-runtime: 2.3.6(supports-color@7.2.0)
html-url-attributes: 3.0.1
mdast-util-to-hast: 13.2.1
react: 19.2.4
- remark-parse: 11.0.0
+ remark-parse: 11.0.0(supports-color@7.2.0)
remark-rehype: 11.1.2
unified: 11.0.5
unist-util-visit: 5.1.0
@@ -10283,21 +10317,21 @@ snapshots:
gopd: 1.2.0
set-function-name: 2.0.2
- remark-gfm@4.0.1:
+ remark-gfm@4.0.1(supports-color@7.2.0):
dependencies:
'@types/mdast': 4.0.4
- mdast-util-gfm: 3.1.0
+ mdast-util-gfm: 3.1.0(supports-color@7.2.0)
micromark-extension-gfm: 3.0.0
- remark-parse: 11.0.0
+ remark-parse: 11.0.0(supports-color@7.2.0)
remark-stringify: 11.0.0
unified: 11.0.5
transitivePeerDependencies:
- supports-color
- remark-parse@11.0.0:
+ remark-parse@11.0.0(supports-color@7.2.0):
dependencies:
'@types/mdast': 4.0.4
- mdast-util-from-markdown: 2.0.3
+ mdast-util-from-markdown: 2.0.3(supports-color@7.2.0)
micromark-util-types: 2.0.2
unified: 11.0.5
transitivePeerDependencies:
@@ -10366,9 +10400,9 @@ snapshots:
'@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17
'@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17
- router@2.2.0:
+ router@2.2.0(supports-color@7.2.0):
dependencies:
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
depd: 2.0.0
is-promise: 4.0.0
parseurl: 1.3.3
@@ -10420,9 +10454,9 @@ snapshots:
semver@7.7.4: {}
- send@1.2.1:
+ send@1.2.1(supports-color@7.2.0):
dependencies:
- debug: 4.4.3
+ debug: 4.4.3(supports-color@7.2.0)
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
@@ -10436,12 +10470,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- serve-static@2.2.1:
+ serve-static@2.2.1(supports-color@7.2.0):
dependencies:
encodeurl: 2.0.0
escape-html: 1.0.3
parseurl: 1.3.3
- send: 1.2.1
+ send: 1.2.1(supports-color@7.2.0)
transitivePeerDependencies:
- supports-color
@@ -10475,14 +10509,14 @@ snapshots:
setprototypeof@1.2.0: {}
- shadcn@4.7.0(@types/node@20.19.39)(typescript@6.0.3):
+ shadcn@4.7.0(@types/node@20.19.39)(supports-color@7.2.0)(typescript@6.0.3):
dependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@7.2.0)
'@babel/parser': 7.29.3
- '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0)
- '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0)
+ '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)
+ '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)
'@dotenvx/dotenvx': 1.65.0
- '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76)
+ '@modelcontextprotocol/sdk': 1.30.0(supports-color@7.2.0)(zod@3.25.76)
'@types/validate-npm-package-name': 4.0.2
browserslist: 4.28.2
commander: 14.0.3
@@ -10494,7 +10528,7 @@ snapshots:
fast-glob: 3.3.3
fs-extra: 11.3.4
fuzzysort: 3.1.0
- https-proxy-agent: 7.0.6
+ https-proxy-agent: 7.0.6(supports-color@7.2.0)
kleur: 4.1.5
msw: 2.14.3(@types/node@20.19.39)(typescript@6.0.3)
node-fetch: 3.3.2
@@ -10725,12 +10759,12 @@ snapshots:
dependencies:
inline-style-parser: 0.2.7
- styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.4):
+ styled-jsx@5.1.6(@babel/core@7.29.0(supports-color@7.2.0))(react@19.2.4):
dependencies:
client-only: 0.0.1
react: 19.2.4
optionalDependencies:
- '@babel/core': 7.29.0
+ '@babel/core': 7.29.0(supports-color@7.2.0)
supports-color@7.2.0:
dependencies:
@@ -10873,13 +10907,13 @@ snapshots:
possible-typed-array-names: 1.1.0
reflect.getprototypeof: 1.0.10
- typescript-eslint@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3):
+ typescript-eslint@8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3):
dependencies:
- '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)
- '@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)
- '@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3)
- '@typescript-eslint/utils': 8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)
- eslint: 9.39.4(jiti@2.7.0)
+ '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)
+ '@typescript-eslint/typescript-estree': 8.59.2(supports-color@7.2.0)(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.59.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)
+ eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -11185,6 +11219,10 @@ snapshots:
dependencies:
zod: 3.25.76
+ zod-to-json-schema@3.25.2(zod@4.4.3):
+ dependencies:
+ zod: 4.4.3
+
zod-validation-error@4.0.2(zod@4.4.3):
dependencies:
zod: 4.4.3
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 80ee5bbc..082ea2dc 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,3 +1,11 @@
+allowBuilds:
+ core-js: set this to true or false
+ esbuild: set this to true or false
+ msgpackr-extract: set this to true or false
+ msw: set this to true or false
+ sharp: set this to true or false
+ unrs-resolver: set this to true or false
+
ignoredBuiltDependencies:
- sharp
- unrs-resolver
diff --git a/src/agent-harness/index.ts b/src/agent-harness/index.ts
index a2666bd4..40e843bb 100644
--- a/src/agent-harness/index.ts
+++ b/src/agent-harness/index.ts
@@ -1,5 +1,6 @@
export * from "./image-highlights"
export * from "./ledger"
export * from "./knowhere-text"
+export * from "./memory-text"
export * from "./runtime"
export * from "./types"
diff --git a/src/agent-harness/memory-text.test.ts b/src/agent-harness/memory-text.test.ts
new file mode 100644
index 00000000..aa1699de
--- /dev/null
+++ b/src/agent-harness/memory-text.test.ts
@@ -0,0 +1,38 @@
+import { describe, expect, it } from "vitest"
+
+import { memoryToolText } from "./memory-text"
+
+describe("memoryToolText", () => {
+ it("formats search results with memory refs and stored summaries", () => {
+ const text = memoryToolText.formatSearch({
+ query: "毛利率",
+ items: [
+ {
+ ref: "mem:1",
+ itemId: "item_1",
+ kind: "stance",
+ abstractL0: "关注毛利率下滑",
+ overviewL1: "用户把毛利率当作核心观察指标。",
+ },
+ ],
+ })
+
+ expect(text).toContain('')
+ expect(text).toContain('query="毛利率"')
+ expect(text).toContain('ref="mem:1"')
+ expect(text).toContain('itemId="item_1"')
+ expect(text).toContain('kind="stance"')
+ expect(text).toContain("关注毛利率下滑")
+ expect(text).toContain("用户把毛利率当作核心观察指标。")
+ })
+
+ it("formats an empty search without inventing items", () => {
+ const text = memoryToolText.formatSearch({
+ query: "unknown",
+ items: [],
+ })
+
+ expect(text).toContain('resultCount="0"')
+ expect(text).not.toContain("- ",
+ ].join("\n")
+ },
+} as const
+
+function formatMemoryItems(response: MemorySearchResponse): string {
+ if (response.items.length === 0) return ""
+
+ return [
+ "",
+ ...response.items.map((item) =>
+ [
+ formatOpenTag("item", {
+ ref: item.ref,
+ itemId: item.itemId,
+ kind: item.kind,
+ }),
+ formatTextTag("abstract_l0", item.abstractL0),
+ formatTextTag("overview_l1", item.overviewL1),
+ "
",
+ ].join("\n"),
+ ),
+ "",
+ ].join("\n")
+}
+
+function wrapMemoryBlock(
+ operation: MemoryOperation,
+ parts: readonly string[],
+): string {
+ return [
+ formatOpenTag("memory", { operation, status: "ok" }),
+ ...parts.filter((part) => part.trim().length > 0),
+ "",
+ ].join("\n")
+}
+
+function formatTextTag(tagName: string, value: string): string {
+ return [`<${tagName}>`, value, `${tagName}>`].join("\n")
+}
+
+function formatTag(
+ tagName: string,
+ attrs: Readonly>,
+): string {
+ return `${formatOpenTag(tagName, attrs)}${tagName}>`
+}
+
+function formatOpenTag(
+ tagName: string,
+ attrs: Readonly>,
+): string {
+ const serializedAttrs = Object.entries(attrs)
+ .filter((entry): entry is [string, string] => typeof entry[1] === "string")
+ .map(([key, value]) => `${key}="${escapeAttribute(value)}"`)
+ .join(" ")
+ return serializedAttrs ? `<${tagName} ${serializedAttrs}>` : `<${tagName}>`
+}
+
+function escapeAttribute(value: string): string {
+ return value
+ .replaceAll("&", "&")
+ .replaceAll('"', """)
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+}
diff --git a/src/agent-harness/runtime.test.ts b/src/agent-harness/runtime.test.ts
index 74e7ef71..b459d5b8 100644
--- a/src/agent-harness/runtime.test.ts
+++ b/src/agent-harness/runtime.test.ts
@@ -16,10 +16,23 @@ import type {
ImageInspectionRequest,
IntentFrame,
KnowhereToolRuntime,
+ MemoryToolRuntime,
OutputManifest,
} from "./types"
describe("agent harness runtime", () => {
+ it("tells the agent to search fluid memory first and not treat every question as document retrieval", () => {
+ const prompt = buildHarnessSystemPrompt(makeTurnInput())
+
+ expect(prompt).toContain("Call memory_search first")
+ expect(prompt).toContain(
+ "Call knowhere_search only when memory is insufficient and groundingPolicy requires citing source documents",
+ )
+ expect(prompt).toContain(
+ "Do not treat every question as a document-retrieval task",
+ )
+ })
+
it("keeps KNOWHERE as an evidence provider instead of exposing internal navigation", () => {
const prompt = buildHarnessSystemPrompt(makeTurnInput())
@@ -72,6 +85,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state,
ledger: createEvidenceLedger(),
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(query),
recentTurns: [],
})
@@ -147,6 +161,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state,
ledger,
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(query),
recentTurns: [],
})
@@ -171,6 +186,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state: {},
ledger: createEvidenceLedger(),
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(),
inspectImages,
recentTurns: [],
@@ -203,6 +219,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state: {},
ledger,
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(),
inspectImages,
recentTurns: [],
@@ -244,6 +261,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state: {},
ledger,
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(),
inspectImages,
recentTurns: [],
@@ -274,6 +292,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state: {},
ledger,
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(),
inspectImages,
recentTurns: [],
@@ -330,6 +349,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state: {},
ledger,
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(),
inspectImages,
recentTurns: [],
@@ -435,6 +455,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state,
ledger,
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(),
inspectImages,
recentTurns: [],
@@ -451,6 +472,7 @@ describe("agent harness runtime", () => {
await executeTool(tools.finalize, {
text: "Revenue was $24.9B [[cite:1]] [[cite:2]].",
citations: [{ ref: "r1:result:1" }, { ref: "r1:result:2" }],
+ memoryCitations: [],
artifacts: [],
unresolved: [],
}),
@@ -523,6 +545,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state: {},
ledger,
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(),
inspectImages,
recentTurns: [],
@@ -547,6 +570,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state,
ledger,
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(),
inspectImages: vi.fn().mockResolvedValue({
analysis: "",
@@ -575,6 +599,7 @@ describe("agent harness runtime", () => {
const finalize = await executeTool(tools.finalize, {
text: "The amount is 5000 yuan [[cite:1]].",
citations: [{ ref: "r1:referenced:1" }],
+ memoryCitations: [],
artifacts: [],
unresolved: [],
})
@@ -593,6 +618,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state,
ledger: createEvidenceLedger(),
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(),
recentTurns: [],
})
@@ -600,6 +626,7 @@ describe("agent harness runtime", () => {
const manifest = {
text: "Answer.",
citations: [],
+ memoryCitations: [],
artifacts: [],
unresolved: [],
}
@@ -612,6 +639,58 @@ describe("agent harness runtime", () => {
expect(state.finalized).toBe(true)
})
+ it("returns memory refs from memory_search and stores memoryCitations on finalize", async () => {
+ const search = vi.fn().mockResolvedValue({
+ query: "毛利率",
+ items: [
+ {
+ ref: "mem:1",
+ itemId: "item_1",
+ kind: "stance",
+ abstractL0: "关注毛利率下滑",
+ overviewL1: "用户把毛利率当作核心观察指标。",
+ },
+ ],
+ })
+ const state: {
+ finalizedManifest?: OutputManifest
+ finalized?: boolean
+ } = {}
+ const tools = createHarnessTools({
+ state,
+ ledger: createEvidenceLedger(),
+ memoryTools: makeMemoryTools(search),
+ knowhereTools: makeKnowhereTools(),
+ recentTurns: [],
+ })
+
+ const searchText = await executeTool(tools.memory_search, {
+ query: "毛利率",
+ })
+ expect(searchText).toContain('')
+ expect(searchText).toContain('ref="mem:1"')
+ expect(searchText).toContain('itemId="item_1"')
+ expect(search).toHaveBeenCalledWith({
+ query: "毛利率",
+ kinds: undefined,
+ })
+
+ const manifest = {
+ text: "按已有记忆,毛利率是核心观察指标。",
+ citations: [],
+ memoryCitations: [
+ { ref: "mem:1", itemId: "item_1", kind: "stance" as const },
+ ],
+ artifacts: [],
+ unresolved: [],
+ }
+ expect(await executeTool(tools.finalize, manifest)).toMatchObject({
+ ok: true,
+ memoryCitations: manifest.memoryCitations,
+ })
+ expect(state.finalizedManifest).toEqual(manifest)
+ })
+
it("rejects finalize of cited page images until inspectImage has run", async () => {
const ledger = createEvidenceLedger()
ledger.addRetrievalResponse(makePageCitationRetrievalResponse())
@@ -623,6 +702,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state,
ledger,
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(),
inspectImages: vi.fn().mockResolvedValue({
analysis: "The clause shows 5000 yuan per occurrence.",
@@ -640,6 +720,7 @@ describe("agent harness runtime", () => {
const manifest = {
text: "The contractor pays 5000 yuan per occurrence [[cite:1]].",
citations: [{ ref: "r1:referenced:1" }],
+ memoryCitations: [],
artifacts: [],
unresolved: [],
}
@@ -692,6 +773,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state: {},
ledger,
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(),
inspectImages: vi.fn(),
recentTurns: [],
@@ -700,6 +782,7 @@ describe("agent harness runtime", () => {
const result = await executeTool(tools.finalize, {
text: "The contractor pays 5000 yuan [[cite:1]].",
citations: [{ ref: "r1:result:1" }],
+ memoryCitations: [],
artifacts: [],
unresolved: [],
})
@@ -725,6 +808,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state,
ledger: createEvidenceLedger(),
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(),
recentTurns: [
{
@@ -757,6 +841,7 @@ describe("agent harness runtime", () => {
const tools = createHarnessTools({
state,
ledger: createEvidenceLedger(),
+ memoryTools: makeMemoryTools(),
knowhereTools: makeKnowhereTools(),
recentTurns: [
{
@@ -881,7 +966,7 @@ describe("agent harness runtime", () => {
])
})
- it("keeps normal steps unconstrained before the finalization step", () => {
+ it("keeps retrieval tools closed until declareIntent allows them", () => {
const result = prepareHarnessStep({
stepNumber: 11,
messages: [
@@ -892,14 +977,77 @@ describe("agent harness runtime", () => {
],
})
- expect(result).toEqual({
- messages: [
- {
- role: "user",
- content: "Find the penalty amount.",
- },
- ],
+ expect(result.activeTools).toEqual([
+ "declareIntent",
+ "setContextPolicy",
+ "inspectImage",
+ "readPriorTurn",
+ "finalize",
+ ])
+ expect(result.activeTools).not.toContain("memory_search")
+ expect(result.activeTools).not.toContain("knowhere_search")
+ })
+
+ it("opens only memory_search after intent says retrieval may be needed", () => {
+ const result = prepareHarnessStep({
+ stepNumber: 3,
+ intent: {
+ task: "answer",
+ dependsOnPreviousTurn: false,
+ retrievalNeeded: "maybe",
+ targetModalities: ["text"],
+ constraints: {},
+ groundingPolicy: "can_use_context",
+ },
+ messages: [],
})
+
+ expect(result.activeTools).toContain("memory_search")
+ expect(result.activeTools).not.toContain("knowhere_search")
+ })
+
+ it("keeps Knowhere tools closed for no_retrieval", () => {
+ const result = prepareHarnessStep({
+ stepNumber: 4,
+ intent: {
+ task: "answer",
+ dependsOnPreviousTurn: false,
+ retrievalNeeded: "no",
+ targetModalities: ["text"],
+ constraints: {},
+ groundingPolicy: "no_retrieval",
+ },
+ messages: [],
+ })
+
+ expect(result.activeTools).not.toContain("memory_search")
+ expect(result.activeTools).not.toContain("knowhere_search")
+ })
+
+ it("opens memory_search and knowhere_search together as peers when sources are required", () => {
+ const result = prepareHarnessStep({
+ stepNumber: 3,
+ intent: {
+ task: "answer",
+ dependsOnPreviousTurn: false,
+ retrievalNeeded: "yes",
+ targetModalities: ["text"],
+ constraints: {},
+ groundingPolicy: "must_use_sources",
+ },
+ messages: [],
+ })
+
+ expect(result.activeTools).toEqual(
+ expect.arrayContaining([
+ "memory_search",
+ "knowhere_search",
+ "knowhere_list_documents",
+ "knowhere_get_document_outline",
+ "knowhere_read_chunks",
+ "knowhere_grep_chunks",
+ ]),
+ )
})
it("forces image inspection before forced finalization when image assets are available", () => {
@@ -1034,6 +1182,14 @@ function executeTool(tool: unknown, input: unknown): Promise {
return (tool as { execute: (input: unknown) => Promise }).execute(input)
}
+function makeMemoryTools(
+ search: MemoryToolRuntime["search"] = vi
+ .fn()
+ .mockResolvedValue({ query: "", items: [] }),
+): MemoryToolRuntime {
+ return { search }
+}
+
function makeKnowhereTools(
search: KnowhereToolRuntime["search"] = vi.fn(),
): KnowhereToolRuntime {
diff --git a/src/agent-harness/runtime.ts b/src/agent-harness/runtime.ts
index 073051dd..0f4aab0a 100644
--- a/src/agent-harness/runtime.ts
+++ b/src/agent-harness/runtime.ts
@@ -10,6 +10,7 @@ import { z } from "zod"
import { createEvidenceLedger } from "./ledger"
import { getCanonicalImageAssetKey } from "./image-asset-identity"
import { knowhereToolText } from "./knowhere-text"
+import { memoryToolText } from "./memory-text"
import { mergeImageInspectionHighlights } from "./image-highlights"
import type {
AgentTurn,
@@ -27,8 +28,11 @@ import type {
IntentFrame,
KnowhereSearchTargetContent,
KnowhereToolRuntime,
+ MemorySearchKind,
+ MemoryToolRuntime,
OutputManifest,
} from "./types"
+import { memorySearchKinds } from "./types"
const defaultMaxSteps = 14
const imageInspectionReminderStepNumber = 12
@@ -42,6 +46,7 @@ export type RunAgentHarnessInput = {
readonly model: AgentHarnessModel
readonly turn: AgentTurnInput
readonly knowhereTools: KnowhereToolRuntime
+ readonly memoryTools: MemoryToolRuntime
readonly inspectImages?: InspectImages
readonly maxSteps?: number
}
@@ -171,6 +176,17 @@ const outputCitationSchema = z.object({
.optional(),
})
+const memoryCitationSchema = z.object({
+ ref: z.string().min(1),
+ itemId: z.string().min(1),
+ kind: z.enum(memorySearchKinds),
+})
+
+const memorySearchSchema = z.object({
+ query: z.string().min(1),
+ kinds: z.array(z.enum(memorySearchKinds)).optional(),
+})
+
const selectedOutputArtifactSchema = z.object({
type: z.enum(["image", "table"]),
ref: z.string().min(1),
@@ -197,6 +213,7 @@ const outputArtifactSchema = z.union([
const outputManifestSchema = z.object({
text: z.string(),
citations: z.array(outputCitationSchema).default([]),
+ memoryCitations: z.array(memoryCitationSchema).default([]),
artifacts: z.array(outputArtifactSchema).default([]),
unresolved: z.array(z.string()).default([]),
})
@@ -214,6 +231,7 @@ export async function runAgentHarness(
state,
ledger,
knowhereTools: input.knowhereTools,
+ memoryTools: input.memoryTools,
inspectImages: input.inspectImages,
recentTurns: input.turn.recentTurns,
})
@@ -225,6 +243,7 @@ export async function runAgentHarness(
prepareHarnessStep({
messages: stepMessages,
stepNumber,
+ intent: state.intent,
hasUninspectedImageAssets:
input.inspectImages !== undefined &&
hasUninspectedImageAssets({ state, ledger }),
@@ -257,10 +276,42 @@ export async function runAgentHarness(
}
}
+const alwaysAvailableTools = [
+ "declareIntent",
+ "setContextPolicy",
+ "inspectImage",
+ "readPriorTurn",
+ "finalize",
+] as const
+
+const fluidRetrievalTools = ["memory_search"] as const
+
+const crystalRetrievalTools = [
+ "knowhere_search",
+ "knowhere_list_documents",
+ "knowhere_get_document_outline",
+ "knowhere_read_chunks",
+ "knowhere_grep_chunks",
+] as const
+
+/** Reserved third retrieval slot (cognition). Not registered this round. */
+const cognitionRetrievalTools = [] as const
+
+// TODO(memory-architecture): today the agent itself decides, per turn via
+// declareIntent, whether to call memory_search / knowhere_search as MCP
+// tools. An alternative considered and deferred: always query Memento
+// (including future "cognition") on every turn and let Memento decide what,
+// if anything, to inject into context, instead of the agent choosing to call
+// a tool. Not adopted now — it would replace this tool-invocation control
+// flow with a middleware/auto-inject model and needs its own design + test
+// rewrite. Revisit if agent misjudgment on retrieval-needed becomes a real
+// problem.
+
export function prepareHarnessStep(input: {
readonly stepNumber: number
readonly messages: readonly ModelMessage[]
readonly hasUninspectedImageAssets?: boolean
+ readonly intent?: IntentFrame
}): HarnessStepPreparation {
const messages = sanitizeHarnessModelMessagesForStep(input.messages)
@@ -285,24 +336,57 @@ export function prepareHarnessStep(input: {
}
}
- if (input.stepNumber < forcedFinalizationStepNumber) {
- return { messages }
+ if (input.stepNumber >= forcedFinalizationStepNumber) {
+ return {
+ messages: [
+ ...messages,
+ {
+ role: "user",
+ content: buildForcedFinalizationFeedback(),
+ },
+ ],
+ activeTools: ["finalize"],
+ toolChoice: {
+ type: "tool",
+ toolName: "finalize",
+ },
+ }
}
return {
- messages: [
- ...messages,
- {
- role: "user",
- content: buildForcedFinalizationFeedback(),
- },
- ],
- activeTools: ["finalize"],
- toolChoice: {
- type: "tool",
- toolName: "finalize",
- },
+ messages,
+ activeTools: selectHarnessActiveTools({
+ intent: input.intent,
+ }),
+ }
+}
+
+function selectHarnessActiveTools(input: {
+ readonly intent?: IntentFrame
+}): Array> {
+ const tools: Array> = [
+ ...alwaysAvailableTools,
+ ]
+ if (!allowsRetrieval(input.intent)) {
+ return tools
+ }
+
+ // memory_search and knowhere_search are peers: both open together once
+ // retrieval is allowed. The agent decides which to call and in what
+ // order — neither tool gates the other.
+ tools.push(...fluidRetrievalTools)
+ if (input.intent?.groundingPolicy === "must_use_sources") {
+ tools.push(...crystalRetrievalTools)
}
+ tools.push(...cognitionRetrievalTools)
+ return tools
+}
+
+function allowsRetrieval(intent?: IntentFrame): boolean {
+ if (!intent) return false
+ return (
+ intent.groundingPolicy !== "no_retrieval" && intent.retrievalNeeded !== "no"
+ )
}
export function sanitizeHarnessModelMessagesForStep(
@@ -410,6 +494,7 @@ export function createHarnessTools(input: {
readonly state: HarnessToolState
readonly ledger: ReturnType
readonly knowhereTools: KnowhereToolRuntime
+ readonly memoryTools: MemoryToolRuntime
readonly inspectImages?: InspectImages
readonly recentTurns: readonly AgentTurn[]
}) {
@@ -446,6 +531,24 @@ export function createHarnessTools(input: {
}),
}),
+ memory_search: tool({
+ description:
+ "Search distilled fluid memory for this workspace. Returns tagged text with memory refs such as mem:1. Use this before Knowhere document search.",
+ inputSchema: memorySearchSchema,
+ execute: async (request) =>
+ traceToolCall(input.state, {
+ toolName: "memory_search",
+ inputSummary: summarizeMemorySearchRequest(request),
+ execute: async () => {
+ return await executeMemorySearch({
+ memoryTools: input.memoryTools,
+ request,
+ })
+ },
+ summarizeOutput: summarizeMemoryTextOutput,
+ }),
+ }),
+
knowhere_search: tool({
description:
"Search Knowhere for relevant Notebook evidence. Returns tagged text with evidence refs such as r1:result:1 and asset refs such as asset:r1:result:1.",
@@ -628,6 +731,7 @@ export function createHarnessTools(input: {
"Finalize the user-facing output manifest. This is the only final answer " +
"contract. Artifacts listed here with display=true are the exact set of " +
"images/tables shown to the user; cite evidence refs when available. " +
+ "Use citations for Knowhere evidence and memoryCitations for fluid memory refs. " +
"Cited page/image assets must be inspected with inspectImage first.",
inputSchema: outputManifestSchema,
execute: async (manifest) =>
@@ -912,6 +1016,7 @@ type KnowhereToolOperation =
| "read_chunks"
| "grep_chunks"
+type MemorySearchToolRequest = z.infer
type KnowhereSearchToolRequest = z.infer
type KnowhereDocumentReferenceRequest = z.infer<
typeof knowhereDocumentReferenceSchema
@@ -925,6 +1030,24 @@ type DocumentReferenceSummary = {
readonly hasRevisionKey: boolean
}
+async function executeMemorySearch(input: {
+ readonly memoryTools: MemoryToolRuntime
+ readonly request: MemorySearchToolRequest
+}): Promise {
+ try {
+ const response = await input.memoryTools.search({
+ query: input.request.query,
+ kinds: input.request.kinds,
+ })
+ return memoryToolText.formatSearch(response)
+ } catch (error) {
+ return memoryToolText.formatError({
+ operation: "search",
+ message: formatUnknownError(error),
+ })
+ }
+}
+
async function executeKnowhereSearch(input: {
readonly ledger: ReturnType
readonly knowhereTools: KnowhereToolRuntime
@@ -1114,6 +1237,25 @@ function summarizeContextPolicy(policy: ContextPolicy): unknown {
}
}
+function summarizeMemorySearchRequest(request: {
+ readonly query: string
+ readonly kinds?: readonly MemorySearchKind[]
+}): unknown {
+ return {
+ query: request.query,
+ kinds: request.kinds,
+ }
+}
+
+function summarizeMemoryTextOutput(output: unknown): unknown {
+ if (typeof output !== "string") return output
+ return {
+ ok: !output.includes('status="error"'),
+ textLength: output.length,
+ itemCount: countOccurrences(output, "- artifact.display)
.length,
@@ -1255,6 +1398,9 @@ function summarizeFinalizeOutput(output: unknown): unknown {
ok: output.ok,
textLength: typeof output.text === "string" ? output.text.length : 0,
citationCount: Array.isArray(output.citations) ? output.citations.length : 0,
+ memoryCitationCount: Array.isArray(output.memoryCitations)
+ ? output.memoryCitations.length
+ : 0,
artifactCount: Array.isArray(output.artifacts) ? output.artifacts.length : 0,
unresolvedCount: Array.isArray(output.unresolved)
? output.unresolved.length
@@ -1282,12 +1428,17 @@ export function buildHarnessSystemPrompt(turn: AgentTurnInput): string {
"1. Call declareIntent when it helps you plan the response. Capture constraints like a requested image/table count in constraints.desiredCount.",
"2. Call setContextPolicy when prior turns may influence this turn.",
"3. When the policy needs prior-turn detail (references or corrections), call readPriorTurn for the relevant ids.",
- "4. Call knowhere_search when relevance search is needed. Use knowhere_list_documents, knowhere_get_document_outline, knowhere_read_chunks, and knowhere_grep_chunks for focused document reads.",
+ "4. Call memory_search first when known fluid memory may answer the request. Call knowhere_search only when memory is insufficient and groundingPolicy requires citing source documents. Use knowhere_list_documents, knowhere_get_document_outline, knowhere_read_chunks, and knowhere_grep_chunks for focused document reads.",
"5. After Knowhere returns image/page asset refs, call inspectImage on the page/image assets you will cite before finalize. This supplies OCR/visual context and provenance boxes.",
"6. Inspect each unique cited page once; retrieval already bounds the available evidence set.",
"7. knowhere_read_chunks returns complete chunk bodies; control size with page/pageSize, sectionPath, startChunk/endChunk, chunkId, and chunkType.",
"8. Call finalize with text, citations, artifacts, and unresolved issues when you are ready to answer.",
"",
+ "Retrieval rules:",
+ "- First use memory_search to see whether known fluid memory can answer directly.",
+ "- Call knowhere_search only when memory is insufficient and groundingPolicy requires citing source documents.",
+ "- Do not treat every question as a document-retrieval task.",
+ "",
"Context rules:",
"- If the current user request is unrelated to prior turns, set carryHistory to none and do not reuse prior topics.",
"- If the user corrects a previous answer, set carryHistory to repair_previous, read the relevant prior turn, then re-retrieve and re-answer using the correction.",
@@ -1347,6 +1498,7 @@ function buildFallbackManifest(text: string): OutputManifest {
return {
text,
citations: [],
+ memoryCitations: [],
artifacts: [],
unresolved: text ? [] : ["The agent did not finalize an output manifest."],
}
diff --git a/src/agent-harness/types.ts b/src/agent-harness/types.ts
index edd9c2f6..82f38ab5 100644
--- a/src/agent-harness/types.ts
+++ b/src/agent-harness/types.ts
@@ -131,6 +131,43 @@ export type KnowhereToolRuntime = {
) => Promise
}
+export const memorySearchKinds = [
+ "indicator_pref",
+ "stance",
+ "decision_rule",
+ "entity_of_interest",
+] as const
+
+export type MemorySearchKind = (typeof memorySearchKinds)[number]
+
+export type MemorySearchRequest = {
+ readonly query: string
+ readonly kinds?: readonly MemorySearchKind[]
+}
+
+export type MemorySearchItem = {
+ readonly ref: string
+ readonly itemId: string
+ readonly kind: MemorySearchKind
+ readonly abstractL0: string
+ readonly overviewL1: string
+}
+
+export type MemorySearchResponse = {
+ readonly query: string
+ readonly items: readonly MemorySearchItem[]
+}
+
+export type MemoryToolRuntime = {
+ readonly search: (input: MemorySearchRequest) => Promise
+}
+
+export type MemoryCitation = {
+ readonly ref: string
+ readonly itemId: string
+ readonly kind: MemorySearchKind
+}
+
export type EvidenceChunk = {
readonly ref: string
readonly kind: "result" | "referenced_chunk" | "read_chunk" | "grep_match"
@@ -254,6 +291,7 @@ export type OutputArtifactView = OutputArtifact | DerivedTableArtifact
export type OutputManifest = {
readonly text: string
readonly citations: readonly OutputCitation[]
+ readonly memoryCitations: readonly MemoryCitation[]
readonly artifacts: readonly OutputArtifactView[]
readonly unresolved: readonly string[]
}
diff --git a/src/app/api/memory/extract/route.ts b/src/app/api/memory/extract/route.ts
deleted file mode 100644
index ee25dbe4..00000000
--- a/src/app/api/memory/extract/route.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { serve } from "@upstash/workflow/nextjs"
-
-import {
- normalizeMemoryExtractPayload,
- runMemoryExtractWorkflow,
- type MemoryExtractPayload,
-} from "@/domains/memory/extract-workflow"
-import { logger } from "@/lib/logger"
-
-export const { POST } = serve(
- async (context) => {
- const payload = normalizeMemoryExtractPayload(context.requestPayload)
- if (!payload) {
- logger.warn("memory: extract workflow received invalid payload")
- return
- }
- await runMemoryExtractWorkflow({ context, payload })
- },
- {
- failureFunction: async ({ context, failResponse }) => {
- logger.error("memory: extract workflow failed", {
- payload: context.requestPayload,
- failResponse,
- })
- },
- },
-)
diff --git a/src/domains/chat/citations.test.ts b/src/domains/chat/citations.test.ts
index b381a83a..1335de3c 100644
--- a/src/domains/chat/citations.test.ts
+++ b/src/domains/chat/citations.test.ts
@@ -64,6 +64,21 @@ describe("toChatCitationViews", () => {
expect(citations[0]?.pageCitationPageNumber).toBe(26)
})
+ it("copies the chunk id onto the citation when the retrieval result has one", () => {
+ const citations = toChatCitationViews(
+ [makeRetrievalResult({ chunkId: "chunk_123" })],
+ "Grounded answer.",
+ )
+
+ expect(citations[0]?.chunkId).toBe("chunk_123")
+ })
+
+ it("omits chunkId when the retrieval result has none", () => {
+ const citations = toChatCitationViews([makeRetrievalResult()], "Grounded answer.")
+
+ expect(citations[0]).not.toHaveProperty("chunkId")
+ })
+
it("copies inspect-image provenance boxes onto the citation", () => {
const citations = toChatCitationViews(
[
diff --git a/src/domains/chat/citations.ts b/src/domains/chat/citations.ts
index 9fb6c8dc..18304aa4 100644
--- a/src/domains/chat/citations.ts
+++ b/src/domains/chat/citations.ts
@@ -20,6 +20,7 @@ export function toChatCitationViews(
content: result.content,
chunkType: result.chunkType,
score: result.score,
+ ...(result.chunkId ? { chunkId: result.chunkId } : {}),
...(result.assetUrl ? { assetUrl: result.assetUrl } : {}),
...(result.pageCitationAssetUrl
? { pageCitationAssetUrl: result.pageCitationAssetUrl }
diff --git a/src/domains/chat/commit-turn.test.ts b/src/domains/chat/commit-turn.test.ts
new file mode 100644
index 00000000..97f19dae
--- /dev/null
+++ b/src/domains/chat/commit-turn.test.ts
@@ -0,0 +1,135 @@
+import { Either } from "effect"
+import { beforeEach, describe, expect, it, vi } from "vitest"
+
+const mocks = vi.hoisted(() => ({
+ handleChatTurn: vi.fn(),
+ captureMemoryTurn: vi.fn(),
+ recordActivations: vi.fn(),
+}))
+
+vi.mock("./service", () => ({
+ handleChatTurn: mocks.handleChatTurn,
+}))
+
+vi.mock("@/integrations/memento/client", () => ({
+ captureMemoryTurn: mocks.captureMemoryTurn,
+ recordActivations: mocks.recordActivations,
+}))
+
+import { commitChatTurn } from "./commit-turn"
+import type { Workspace } from "@/infrastructure/db/schema"
+
+describe("commitChatTurn", () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ mocks.recordActivations.mockResolvedValue(undefined)
+ mocks.captureMemoryTurn.mockResolvedValue(undefined)
+ })
+
+ it("records fluid memory activations from finalize after a successful turn", async () => {
+ mocks.handleChatTurn.mockImplementation(async (input) => {
+ await input.generateAnswer({
+ question: "毛利率",
+ messages: [],
+ sources: [],
+ excludedSourceIds: [],
+ searchSources: vi.fn(),
+ })
+ return Either.right({
+ threadId: "thread_1",
+ messages: [
+ { id: "msg_user", role: "user", content: "毛利率" },
+ {
+ id: "msg_assistant",
+ role: "assistant",
+ content: "按已有记忆。",
+ citations: [
+ {
+ chunkId: "chunk_1",
+ chunkType: "text",
+ score: 0.9,
+ source: { documentId: "doc_1" },
+ },
+ ],
+ },
+ ],
+ })
+ })
+
+ const generateAnswer = vi.fn(async () => ({
+ manifest: {
+ text: "按已有记忆。",
+ citations: [],
+ memoryCitations: [
+ { ref: "mem:1", itemId: "item_1", kind: "stance" as const },
+ ],
+ artifacts: [],
+ unresolved: [],
+ },
+ trace: {
+ ledger: {
+ retrievalCount: 0,
+ chunks: [],
+ assets: [],
+ evidenceText: [],
+ stopReasons: [],
+ failureReasons: [],
+ decisionTraces: [],
+ },
+ finalized: true,
+ priorTurnReads: [],
+ toolCalls: [],
+ imageHighlights: [],
+ validationErrors: [],
+ revisionsUsed: 0,
+ },
+ }))
+
+ const result = await commitChatTurn({
+ workspace: makeWorkspace(),
+ sources: [],
+ question: "毛利率",
+ excludedSourceIds: [],
+ retrieval: { query: vi.fn() },
+ generateAnswer,
+ repository: {
+ ensureDefaultChatThread: vi.fn(),
+ findChatThreadInWorkspace: vi.fn(),
+ listMessagesForThread: vi.fn(),
+ appendMessageToThread: vi.fn(),
+ },
+ })
+
+ expect(Either.isRight(result)).toBe(true)
+ expect(mocks.captureMemoryTurn).toHaveBeenCalledWith({
+ workspaceId: "workspace_1",
+ sourceMessageId: "msg_assistant",
+ userText: "毛利率",
+ assistantText: "按已有记忆。",
+ referencedDocumentIds: ["doc_1"],
+ })
+ expect(mocks.recordActivations).toHaveBeenCalledWith([
+ {
+ workspaceId: "workspace_1",
+ unitType: "crystal_chunk",
+ unitRef: "doc_1:chunk_1",
+ },
+ ])
+ expect(mocks.recordActivations).toHaveBeenCalledWith([
+ {
+ workspaceId: "workspace_1",
+ unitType: "fluid_memory",
+ unitRef: "item_1",
+ },
+ ])
+ })
+})
+
+function makeWorkspace(): Workspace {
+ return {
+ id: "workspace_1",
+ userId: "user_1",
+ namespace: "notebook-namespace",
+ createdAt: new Date("2026-09-10T00:00:00Z"),
+ }
+}
diff --git a/src/domains/chat/commit-turn.ts b/src/domains/chat/commit-turn.ts
new file mode 100644
index 00000000..1113058b
--- /dev/null
+++ b/src/domains/chat/commit-turn.ts
@@ -0,0 +1,133 @@
+import { Either } from "effect"
+
+import type { MemoryCitation } from "@/agent-harness"
+import {
+ captureMemoryTurn,
+ recordActivations,
+} from "@/integrations/memento/client"
+import { generateAgenticOutputManifest } from "./prompt"
+import type { ChatCitationView } from "./types"
+import {
+ handleChatTurn,
+ type ChatTurnError,
+ type ChatTurnValue,
+} from "./service"
+
+type CommitChatTurnInput = Parameters[0]
+
+/**
+ * Production chat-turn commit used by the HTTP route: answer → persist →
+ * capture fluid memory on Memento → record cited crystal/memory activations.
+ */
+export async function commitChatTurn(
+ input: CommitChatTurnInput,
+): Promise> {
+ let memoryCitations: readonly MemoryCitation[] = []
+ const result = await handleChatTurn({
+ ...input,
+ generateAnswer: async (generateInput) => {
+ const generated = await input.generateAnswer({
+ ...generateInput,
+ })
+ memoryCitations = generated.manifest.memoryCitations
+ return generated
+ },
+ })
+
+ if (Either.isRight(result)) {
+ const [userMessage, assistantMessage] = result.right.messages
+ void captureMemoryTurn({
+ workspaceId: input.workspace.id,
+ sourceMessageId: assistantMessage.id,
+ userText: userMessage.content,
+ assistantText: assistantMessage.content,
+ referencedDocumentIds: collectCitationDocumentIds(
+ assistantMessage.citations,
+ ),
+ })
+ void recordChunkActivations({
+ workspaceId: input.workspace.id,
+ citations: assistantMessage.citations,
+ })
+ void recordMemoryActivations({
+ workspaceId: input.workspace.id,
+ memoryCitations,
+ })
+ }
+
+ return result
+}
+
+export async function commitAgenticChatTurn(
+ input: Omit,
+): Promise> {
+ return commitChatTurn({
+ ...input,
+ generateAnswer: (generateInput) =>
+ generateAgenticOutputManifest({
+ ...generateInput,
+ workspaceId: input.workspace.id,
+ }),
+ })
+}
+
+/**
+ * Fire-and-forget activation ledger write for crystal chunks actually cited
+ * in this turn's answer. Only citations with both a documentId and a
+ * chunkId count — a citation missing either can't be identified down to a
+ * chunk (see ChatCitationView / RetrievalResultView).
+ */
+export async function recordChunkActivations(input: {
+ readonly workspaceId: string
+ readonly citations: readonly ChatCitationView[] | undefined
+}): Promise {
+ const activationInputs = (input.citations ?? []).flatMap((citation) => {
+ const documentId = citation.source.documentId
+ const chunkId = citation.chunkId
+ if (!documentId || !chunkId) return []
+ return [
+ {
+ workspaceId: input.workspaceId,
+ unitType: "crystal_chunk" as const,
+ unitRef: toChunkUnitRef({ documentId, chunkId }),
+ },
+ ]
+ })
+ await recordActivations(activationInputs)
+}
+
+/**
+ * Fire-and-forget activation ledger write for fluid memory items actually
+ * cited in this turn's finalize output.
+ */
+export async function recordMemoryActivations(input: {
+ readonly workspaceId: string
+ readonly memoryCitations: readonly MemoryCitation[]
+}): Promise {
+ const activationInputs = input.memoryCitations.map((citation) => ({
+ workspaceId: input.workspaceId,
+ unitType: "fluid_memory" as const,
+ unitRef: citation.itemId,
+ }))
+ await recordActivations(activationInputs)
+}
+
+function toChunkUnitRef(input: {
+ readonly documentId: string
+ readonly chunkId: string
+}): string {
+ return `${input.documentId}:${input.chunkId}`
+}
+
+function collectCitationDocumentIds(
+ citations: readonly ChatCitationView[] | undefined,
+): string[] {
+ const ids = new Set()
+ for (const citation of citations ?? []) {
+ const documentId = citation.source.documentId
+ if (typeof documentId === "string" && documentId.length > 0) {
+ ids.add(documentId)
+ }
+ }
+ return [...ids]
+}
diff --git a/src/domains/chat/index.test.ts b/src/domains/chat/index.test.ts
index 1bc9b606..021ebb22 100644
--- a/src/domains/chat/index.test.ts
+++ b/src/domains/chat/index.test.ts
@@ -1040,6 +1040,7 @@ describe("answerQuestionWithRetrieval", () => {
manifest: {
text: `Use this image. ${rawAssetUrl}`,
citations: [],
+ memoryCitations: [],
artifacts: [
{
type: "image",
@@ -1773,6 +1774,7 @@ describe("answerQuestionWithRetrieval", () => {
manifest: {
text: "已找到相关身份证图片,见下方图片。",
citations: [],
+ memoryCitations: [],
artifacts: [
{
type: "image",
@@ -2063,6 +2065,7 @@ describe("answerQuestionWithRetrieval", () => {
},
},
],
+ memoryCitations: [],
artifacts: [],
unresolved: [],
});
@@ -2081,7 +2084,11 @@ describe("answerQuestionWithRetrieval", () => {
sources: [makeSource()],
excludedSourceIds: [],
retrieval,
- generateAnswer: generateAgenticOutputManifest,
+ generateAnswer: (input) =>
+ generateAgenticOutputManifest({
+ ...input,
+ workspaceId: "workspace_1",
+ }),
messages: [],
}),
);
@@ -2128,6 +2135,7 @@ describe("answerQuestionWithRetrieval", () => {
manifest: {
text: "",
citations: [],
+ memoryCitations: [],
artifacts: [
{
type: "image",
@@ -2243,6 +2251,7 @@ describe("answerQuestionWithRetrieval", () => {
manifest: {
text: "I organized the comparison into a table.",
citations: [],
+ memoryCitations: [],
artifacts: [
{
type: "derived_table",
@@ -2639,6 +2648,7 @@ describe("answerQuestionWithRetrieval", () => {
content: "",
chunkType: "image",
score: null,
+ chunkId: "chunk_1",
assetUrl: "https://blob.example/images/launch.jpg",
source: {
documentId: "doc_spacex",
@@ -2699,6 +2709,7 @@ describe("generateAgenticOutputManifest", () => {
},
},
],
+ memoryCitations: [],
artifacts: [
{
type: "image",
@@ -2739,6 +2750,7 @@ describe("generateAgenticOutputManifest", () => {
});
const result = await generateAgenticOutputManifest({
+ workspaceId: "workspace_1",
question: "请只返回冯荣洲的 2 张身份证图片",
messages: [
{
@@ -2836,6 +2848,7 @@ describe("generateAgenticOutputManifest", () => {
},
},
],
+ memoryCitations: [],
artifacts: [
{
type: "image",
@@ -2885,6 +2898,7 @@ describe("generateAgenticOutputManifest", () => {
});
const result = await generateAgenticOutputManifest({
+ workspaceId: "workspace_1",
question: "Inspect and show the ID card image.",
messages: [],
sources: [
@@ -2976,6 +2990,7 @@ describe("generateAgenticOutputManifest", () => {
},
},
],
+ memoryCitations: [],
artifacts: [],
unresolved: [],
});
@@ -3029,6 +3044,7 @@ describe("generateAgenticOutputManifest", () => {
});
const result = await generateAgenticOutputManifest({
+ workspaceId: "workspace_1",
question: "承包人自行修改发包人审批的进度时需要赔偿多少违约金?",
messages: [],
sources: [
@@ -3126,6 +3142,7 @@ describe("generateAgenticOutputManifest", () => {
},
},
],
+ memoryCitations: [],
artifacts: [1, 2, 3].map((index) => ({
type: "image",
ref: `asset:r1:result:${index}`,
@@ -3148,6 +3165,7 @@ describe("generateAgenticOutputManifest", () => {
},
},
],
+ memoryCitations: [],
artifacts: [1, 2].map((index) => ({
type: "image",
ref: `asset:r1:result:${index}`,
@@ -3188,6 +3206,7 @@ describe("generateAgenticOutputManifest", () => {
});
const result = await generateAgenticOutputManifest({
+ workspaceId: "workspace_1",
question: "只要 2 张身份证图片",
messages: [],
sources: [
@@ -3322,6 +3341,7 @@ function makeHarnessRunResult(text: string): HarnessRunResult {
manifest: {
text,
citations: [],
+ memoryCitations: [],
artifacts: [],
unresolved: [],
},
@@ -3371,6 +3391,7 @@ function makeHarnessRunResultWithLedger(
manifest: {
text,
citations: input.citations ?? [],
+ memoryCitations: [],
artifacts: input.artifacts ?? [],
unresolved: [],
},
diff --git a/src/domains/chat/prompt.ts b/src/domains/chat/prompt.ts
index a4ae326d..4f721019 100644
--- a/src/domains/chat/prompt.ts
+++ b/src/domains/chat/prompt.ts
@@ -16,6 +16,7 @@ import type {
ChatHistoryMessage,
SearchSources,
} from "./contracts"
+import { mementoMemoryTools } from "@/integrations/memento/memory-tools"
import { notebookKnowhereTools } from "./knowhere-tools"
const RECENT_CONTEXT_MESSAGE_LIMIT = 8
@@ -23,6 +24,7 @@ const CONTEXT_CONTENT_CHAR_LIMIT = 900
const SOURCE_CONTEXT_LIMIT = 12
type GenerateAgenticOutputManifestInput = {
+ workspaceId: string
question: string
messages: readonly ChatHistoryMessage[]
sources: readonly Source[]
@@ -63,6 +65,9 @@ export const generateAgenticOutputManifestEffect = (
notebookKnowhereTools.createSearchOnlyRuntime({
searchSources: input.searchSources,
}),
+ memoryTools: mementoMemoryTools.createRuntime({
+ workspaceId: input.workspaceId,
+ }),
...(input.inspectImages ? { inspectImages: input.inspectImages } : {}),
}),
)
diff --git a/src/domains/chat/route-answer.ts b/src/domains/chat/route-answer.ts
index cc579b05..0a7cbb4d 100644
--- a/src/domains/chat/route-answer.ts
+++ b/src/domains/chat/route-answer.ts
@@ -1,9 +1,6 @@
import { Cause, Effect, Either, Option } from "effect"
-import {
- generateAgenticOutputManifest,
- parseChatRequestBody,
-} from "@/domains/chat"
+import { parseChatRequestBody } from "@/domains/chat"
import type {
ImageInspectionAsset,
ImageInspectionRequest,
@@ -11,16 +8,15 @@ import type {
ImageInspectionSkippedAsset,
InspectImages,
} from "@/agent-harness"
+import { commitAgenticChatTurn } from "@/domains/chat/commit-turn"
import { normalizeImageInspectionHighlights } from "@/agent-harness/image-highlights"
import { generateImageInspectionModelResult } from "@/domains/chat/image-inspection-model"
import { hardenChatMediaAssetUrls } from "@/domains/chat/media-asset-hardening"
import {
- handleChatTurn,
type ChatTurnError,
type ChatTurnValue,
} from "@/domains/chat/service"
import { chatTurnPersistence } from "@/domains/chat/chat-turn-persistence"
-import { triggerMemoryExtraction } from "@/domains/memory/extract-trigger"
import { startBackgroundReconciliation } from "@/domains/sources/background-reconcile"
import { BlobParsedDocumentStorage } from "@/domains/sources/parsed-document-blob-storage"
import { sourceWorkflowRuntime } from "@/domains/sources/workflow-runtime"
@@ -140,7 +136,7 @@ const answerChatEffect = (input: AnswerChatInput) =>
const result: Either.Either =
yield* Effect.tryPromise(() =>
- handleChatTurn({
+ commitAgenticChatTurn({
workspace,
sources,
question: body.value.question,
@@ -150,7 +146,6 @@ const answerChatEffect = (input: AnswerChatInput) =>
retrieval: client.retrieval,
knowledge: knowhereResources.knowledge,
remoteDocumentClient: client,
- generateAnswer: generateAgenticOutputManifest,
hardenChatAssetUrl,
hardenMediaAssetUrls: ({ results, artifacts }) =>
hardenChatMediaAssetUrls({
@@ -197,17 +192,8 @@ const answerChatEffect = (input: AnswerChatInput) =>
return Either.match(result, {
onLeft: (error): RouteResponse =>
routeResult.error(error.status, error.message),
- onRight: (value): RouteResponse => {
- // Fire-and-forget: extract fluid memory from this turn without
- // blocking the chat response.
- void triggerMemoryExtraction({
- workspaceId: workspace.id,
- threadId: value.threadId,
- userMessageId: value.messages[0].id,
- assistantMessageId: value.messages[1].id,
- })
- return routeResult.ok(value)
- },
+ onRight: (value): RouteResponse =>
+ routeResult.ok(value),
})
})
diff --git a/src/domains/chat/route-service.test.ts b/src/domains/chat/route-service.test.ts
index 02eceb9b..d27b32dd 100644
--- a/src/domains/chat/route-service.test.ts
+++ b/src/domains/chat/route-service.test.ts
@@ -25,7 +25,8 @@ const mocks = vi.hoisted(() => ({
parsedStorageWriteAsset: vi.fn(),
softDeleteChatThread: vi.fn(),
startBackgroundReconciliation: vi.fn(),
- triggerMemoryExtraction: vi.fn(),
+ captureMemoryTurn: vi.fn(),
+ recordActivations: vi.fn(),
}))
vi.mock("ai", async (importOriginal) => {
@@ -63,8 +64,9 @@ vi.mock("@/domains/sources/background-reconcile", () => ({
startBackgroundReconciliation: mocks.startBackgroundReconciliation,
}))
-vi.mock("@/domains/memory/extract-trigger", () => ({
- triggerMemoryExtraction: mocks.triggerMemoryExtraction,
+vi.mock("@/integrations/memento/client", () => ({
+ captureMemoryTurn: mocks.captureMemoryTurn,
+ recordActivations: mocks.recordActivations,
}))
vi.mock("@/domains/sources/workflow-runtime", () => ({
@@ -191,7 +193,7 @@ describe("chat route services", () => {
useAgentic: true,
excludedSourceIds: ["source_skipped"],
retrieval: client.retrieval,
- generateAnswer: mocks.generateAgenticOutputManifest,
+ generateAnswer: expect.any(Function),
hardenChatAssetUrl: expect.any(Function),
repository: expect.objectContaining({
appendMessageToThread: expect.any(Function),
@@ -853,11 +855,12 @@ describe("chat route services", () => {
})
expect(result.status).toBe(200)
- expect(mocks.triggerMemoryExtraction).toHaveBeenCalledWith({
+ expect(mocks.captureMemoryTurn).toHaveBeenCalledWith({
workspaceId: workspace.id,
- threadId: "thread_1",
- userMessageId: "message_user",
- assistantMessageId: "message_assistant",
+ sourceMessageId: "message_assistant",
+ userText: "Summarize it",
+ assistantText: "Summary",
+ referencedDocumentIds: [],
})
expect(mocks.startBackgroundReconciliation).toHaveBeenCalledWith(
workspace.id,
diff --git a/src/domains/chat/service.test.ts b/src/domains/chat/service.test.ts
index b0652519..4aa1a5a0 100644
--- a/src/domains/chat/service.test.ts
+++ b/src/domains/chat/service.test.ts
@@ -54,7 +54,7 @@ describe("handleChatTurn", () => {
});
}
expect(retrieval.query).toHaveBeenCalledWith({
- namespace: "notebook-namespace",
+ namespace: "default",
query: "What does the document say?",
topK: 8,
useAgentic: true,
@@ -83,6 +83,31 @@ describe("handleChatTurn", () => {
});
});
+ it("rejects a turn with no local sources without calling retrieval", async () => {
+ const retrieval = { query: vi.fn() };
+ const repository = makeRepository();
+
+ const result = await handleChatTurn({
+ workspace: makeWorkspace(),
+ sources: [],
+ question: "What does the document say?",
+ excludedSourceIds: [],
+ retrieval,
+ generateAnswer: vi.fn(),
+ repository,
+ });
+
+ expect(Either.isLeft(result)).toBe(true);
+ if (Either.isLeft(result)) {
+ expect(result.left).toMatchObject({
+ status: 409,
+ message: "Upload and process a document before asking questions.",
+ });
+ }
+ expect(retrieval.query).not.toHaveBeenCalled();
+ expect(repository.appendMessageToThread).not.toHaveBeenCalled();
+ });
+
it("rejects chat before any source is ready without calling retrieval", async () => {
const retrieval = { query: vi.fn() };
const repository = makeRepository();
@@ -220,7 +245,7 @@ describe("handleChatTurn", () => {
knowhereTools: expect.any(Object),
});
expect(retrieval.query).toHaveBeenCalledWith({
- namespace: "notebook-namespace",
+ namespace: "default",
query: "Tesla Q4 2025 Update energy generation and storage deployments",
topK: 8,
useAgentic: true,
@@ -334,6 +359,7 @@ function makeHarnessRunResult(text: string): HarnessRunResult {
manifest: {
text,
citations: [],
+ memoryCitations: [],
artifacts: [],
unresolved: [],
},
diff --git a/src/domains/chat/service.ts b/src/domains/chat/service.ts
index 23bfa68d..7839ab02 100644
--- a/src/domains/chat/service.ts
+++ b/src/domains/chat/service.ts
@@ -9,7 +9,7 @@ import {
} from "."
import { toChatMessageView } from "./view"
import type { ChatMessage, ChatThread, Source, Workspace } from "@/infrastructure/db/schema"
-import { getCompatibleNamespaces } from "@/domains/sources/namespace"
+import { sharedLibraryNamespace } from "@/domains/sources/namespace"
import type {
ChatArtifactView,
ChatCitationView,
@@ -125,7 +125,7 @@ export const handleChatTurnEffect = (input: ChatTurnInput) =>
const answer = yield* answerQuestionWithRetrieval({
question: input.question,
namespace: input.workspace.namespace,
- namespaces: getCompatibleNamespaces(input.workspace),
+ namespaces: [sharedLibraryNamespace],
sources: readySources,
useAgentic: input.useAgentic ?? true,
excludedSourceIds: input.excludedSourceIds,
diff --git a/src/domains/chat/types.ts b/src/domains/chat/types.ts
index 1d6f88f2..e10644f6 100644
--- a/src/domains/chat/types.ts
+++ b/src/domains/chat/types.ts
@@ -5,6 +5,7 @@ export type RetrievalResultView = {
readonly content: string
readonly chunkType: string
readonly score: number | null
+ readonly chunkId?: string
readonly assetUrl?: string
readonly pageCitationAssetUrl?: string
readonly pageCitationPageNumber?: number
diff --git a/src/domains/memory/extract-trigger.ts b/src/domains/memory/extract-trigger.ts
deleted file mode 100644
index 35b64675..00000000
--- a/src/domains/memory/extract-trigger.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import "server-only"
-
-import { Client } from "@upstash/workflow"
-
-import type { MemoryExtractPayload } from "./extract-workflow"
-import { logger } from "@/lib/logger"
-
-function resolveBaseURL(): string {
- return process.env.NOTEBOOK_PUBLIC_URL ?? "http://localhost:3000"
-}
-
-/**
- * Fire-and-forget trigger for the post-turn fluid-memory extraction
- * workflow. Each turn is uniquely keyed by its message ids, so unlike the
- * source reconcile trigger no cooldown/dedup guard is needed; QStash
- * retries cover transient delivery failures.
- */
-export async function triggerMemoryExtraction(
- payload: MemoryExtractPayload,
-): Promise {
- const token = process.env.QSTASH_TOKEN
- if (!token) {
- logger.warn("memory: skipping extraction — QSTASH_TOKEN not set", {
- workspaceId: payload.workspaceId,
- assistantMessageId: payload.assistantMessageId,
- })
- return
- }
-
- try {
- await new Client({ token }).trigger({
- url: `${resolveBaseURL()}/api/memory/extract`,
- body: payload,
- retries: 3,
- })
- } catch (error) {
- logger.error("memory: failed to trigger extraction workflow", {
- workspaceId: payload.workspaceId,
- assistantMessageId: payload.assistantMessageId,
- message: error instanceof Error ? error.message : String(error),
- })
- }
-}
diff --git a/src/domains/memory/extract-workflow.ts b/src/domains/memory/extract-workflow.ts
deleted file mode 100644
index aa6b5ee3..00000000
--- a/src/domains/memory/extract-workflow.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-import "server-only"
-
-import type { WorkflowContext } from "@upstash/workflow"
-
-import { extractMemoryOperations } from "./extraction-model"
-import {
- summarizePayloadForContext,
- type ExistingMemoryContextItem,
-} from "./prompts"
-import { resolveMemoryOperations } from "./resolve-operations"
-import { tokenizeMemoryText } from "./search-index"
-import { memoryService } from "./service"
-import { fluidMemoryKinds, isFluidMemoryKind } from "./types"
-import { chatThreadService } from "@/domains/chat/thread-service"
-import { logger } from "@/lib/logger"
-
-export type MemoryExtractPayload = {
- readonly workspaceId: string
- readonly threadId: string
- readonly userMessageId: string
- readonly assistantMessageId: string
-}
-
-type MemoryExtractWorkflowContext = Pick<
- WorkflowContext,
- "run"
->
-
-/** Prompt-context item that also carries status/payload for resolution. */
-type MemoryWorkflowItem = ExistingMemoryContextItem & {
- readonly status: string
- readonly payload: unknown
-}
-
-/** Per-kind cap on lexical neighbors fed into the merge-decision prompt. */
-const DEDUP_CANDIDATES_PER_KIND = 8
-
-export function normalizeMemoryExtractPayload(
- raw: unknown,
-): MemoryExtractPayload | null {
- if (!raw || typeof raw !== "object") return null
- const record = raw as Record
- const workspaceId = getNonEmptyString(record.workspaceId)
- const threadId = getNonEmptyString(record.threadId)
- const userMessageId = getNonEmptyString(record.userMessageId)
- const assistantMessageId = getNonEmptyString(record.assistantMessageId)
- if (!workspaceId || !threadId || !userMessageId || !assistantMessageId) {
- return null
- }
- return { workspaceId, threadId, userMessageId, assistantMessageId }
-}
-
-export async function runMemoryExtractWorkflow(input: {
- readonly context: MemoryExtractWorkflowContext
- readonly payload: MemoryExtractPayload
-}): Promise {
- const { context, payload } = input
-
- const turn = await context.run("load-turn", async () => {
- const messages = await chatThreadService.listMessages(
- payload.workspaceId,
- payload.threadId,
- )
- const userMessage = messages?.find(
- (message) => message.id === payload.userMessageId,
- )
- const assistantMessage = messages?.find(
- (message) => message.id === payload.assistantMessageId,
- )
- if (!userMessage || !assistantMessage) return null
- return {
- userText: userMessage.content,
- assistantText: assistantMessage.content,
- referencedDocumentIds: collectCitationDocumentIds(
- assistantMessage.citations,
- ),
- }
- })
- if (!turn) {
- logger.warn("memory: extract skipped — turn messages not found", {
- workspaceId: payload.workspaceId,
- threadId: payload.threadId,
- })
- return
- }
-
- const existingItems = await context.run("retrieve-candidates", async () => {
- const queryTokens = tokenizeMemoryText(turn.userText).map(
- (entry) => entry.token,
- )
- if (queryTokens.length === 0) return []
-
- const byId = new Map()
- for (const kind of fluidMemoryKinds) {
- const items = await memoryService.findDedupCandidates(
- payload.workspaceId,
- kind,
- queryTokens,
- DEDUP_CANDIDATES_PER_KIND,
- )
- for (const item of items) {
- if (!isFluidMemoryKind(item.kind) || byId.has(item.id)) continue
- byId.set(item.id, {
- id: item.id,
- kind: item.kind,
- status: item.status,
- payload: item.payload,
- abstractL0: item.abstractL0,
- payloadSummary: summarizePayloadForContext(item.kind, item.payload),
- })
- }
- }
- return [...byId.values()]
- })
-
- const operations = await context.run("extract-operations", () =>
- extractMemoryOperations({
- workspaceId: payload.workspaceId,
- userText: turn.userText,
- assistantText: turn.assistantText,
- referencedDocumentIds: turn.referencedDocumentIds,
- existingItems,
- }),
- )
- if (!operations) return
-
- const applied = await context.run("apply-operations", async () => {
- const resolved = resolveMemoryOperations({
- operations,
- existingItems,
- referencedDocumentIds: turn.referencedDocumentIds,
- })
- if (resolved.length === 0) return null
- return memoryService.applyOperations(
- payload.workspaceId,
- payload.assistantMessageId,
- resolved,
- )
- })
-
- logger.info("memory: extract workflow finished", {
- workspaceId: payload.workspaceId,
- threadId: payload.threadId,
- assistantMessageId: payload.assistantMessageId,
- candidateCount: existingItems.length,
- appliedOperations: applied?.map((operation) => operation.op) ?? [],
- })
-}
-
-function collectCitationDocumentIds(citations: unknown): string[] {
- if (!Array.isArray(citations)) return []
- const ids = new Set()
- for (const citation of citations) {
- if (!citation || typeof citation !== "object") continue
- const source = (citation as Record).source
- if (!source || typeof source !== "object") continue
- const documentId = (source as Record).documentId
- if (typeof documentId === "string" && documentId.length > 0) {
- ids.add(documentId)
- }
- }
- return [...ids]
-}
-
-function getNonEmptyString(value: unknown): string | null {
- return typeof value === "string" && value.trim().length > 0
- ? value
- : null
-}
diff --git a/src/domains/memory/extraction-model.ts b/src/domains/memory/extraction-model.ts
deleted file mode 100644
index fa490115..00000000
--- a/src/domains/memory/extraction-model.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import "server-only"
-
-import { generateObject } from "ai"
-
-import {
- buildMemoryExtractionPrompt,
- memoryOperationsSchema,
- type ExistingMemoryContextItem,
- type MemoryOperations,
-} from "./prompts"
-import { CHAT_MODEL } from "@/lib/ai"
-import { summarizeUnknownError } from "@/lib/format-log-value"
-import { logger } from "@/lib/logger"
-
-const MEMORY_EXTRACTION_MODEL = process.env.MEMORY_EXTRACTION_MODEL ?? CHAT_MODEL
-
-/**
- * One structured-output call: turn + existing active memories in, typed
- * operations out. Best-effort by design — this runs as a background job,
- * so a model failure skips the turn (logged) instead of degrading through
- * fallbacks; the insight typically resurfaces in a later turn.
- */
-export async function extractMemoryOperations(input: {
- readonly workspaceId: string
- readonly userText: string
- readonly assistantText: string
- readonly referencedDocumentIds: readonly string[]
- readonly existingItems: readonly ExistingMemoryContextItem[]
-}): Promise {
- try {
- const response = await generateObject({
- model: MEMORY_EXTRACTION_MODEL,
- schema: memoryOperationsSchema,
- messages: [
- {
- role: "user",
- content: buildMemoryExtractionPrompt(input),
- },
- ],
- })
- return response.object
- } catch (error) {
- logger.warn("memory: extraction model call failed; skipping turn", {
- workspaceId: input.workspaceId,
- model: MEMORY_EXTRACTION_MODEL,
- existingItemCount: input.existingItems.length,
- error: summarizeUnknownError(error),
- })
- return null
- }
-}
diff --git a/src/domains/memory/prompts.test.ts b/src/domains/memory/prompts.test.ts
deleted file mode 100644
index 2ae15ff2..00000000
--- a/src/domains/memory/prompts.test.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { describe, expect, it } from "vitest"
-
-import { buildMemoryExtractionPrompt } from "./prompts"
-
-describe("buildMemoryExtractionPrompt", () => {
- const prompt = buildMemoryExtractionPrompt({
- userText: "毛利率是核心。",
- assistantText: "明白。",
- referencedDocumentIds: ["doc-1"],
- existingItems: [],
- })
-
- it("keeps main instructions domain-agnostic", () => {
- const main = prompt.slice(0, prompt.indexOf("## Illustrative examples"))
- expect(main).not.toMatch(/gross margin|市盈率|\bPE\b|Fed|美联储|NVIDIA|英伟达/i)
- expect(main).toContain("Write every free-text value")
- expect(main).toMatch(/same language the\s+USER wrote/)
- })
-
- it("keeps illustrative examples in a separate section", () => {
- expect(prompt).toContain("## Illustrative examples (finance vertical")
- expect(prompt).toContain("not exhaustive, not required vocabulary")
- const examples = prompt.slice(
- prompt.indexOf("## Illustrative examples"),
- prompt.indexOf("## Output JSON schema"),
- )
- expect(examples).toContain("finance vertical")
- expect(examples).toContain("do not force the conversation into this domain")
- })
-
- it("still injects turn context after the fixed blocks", () => {
- expect(prompt).toContain("[user]\n毛利率是核心。")
- expect(prompt).toContain("doc-1")
- })
-})
diff --git a/src/domains/memory/prompts.ts b/src/domains/memory/prompts.ts
deleted file mode 100644
index e2fbeb0c..00000000
--- a/src/domains/memory/prompts.ts
+++ /dev/null
@@ -1,285 +0,0 @@
-import { z } from "zod"
-
-import {
- decisionRulePayloadSchema,
- entityOfInterestPayloadSchema,
- indicatorPreferencePayloadSchema,
- stancePayloadSchema,
- type FluidMemoryKind,
-} from "./types"
-
-/**
- * LLM contract for fluid-memory extraction.
- *
- * Design borrowed from OpenViking's session-commit extraction (schema-driven
- * typed operations, prefetch-then-decide), reimplemented as a single
- * structured-output call: the model sees the turn plus lexically retrieved
- * dedup candidates and directly outputs per-kind operations
- * (create / skip / merge / deprecate), mirroring OpenViking's generated
- * operations model without its ReAct tool loop.
- */
-
-const decisionSchema = z.object({
- op: z.enum(["create", "skip", "merge", "deprecate"]),
- targetItemId: z.preprocess(
- (value) => (value === null ? undefined : value),
- z
- .string()
- .optional()
- .describe(
- "Required for merge/deprecate: the id of the existing memory item this operation targets. Omit for create/skip.",
- ),
- ),
- reason: z.preprocess(
- (value) => (value === null ? undefined : value),
- z
- .string()
- .optional()
- .describe("Short justification, especially for skip/merge/deprecate."),
- ),
-})
-
-const memorySidecarFields = {
- abstractL0: z
- .string()
- .min(1)
- .describe("One line, <= 30 words: the essence of this insight."),
- overviewL1: z
- .string()
- .min(1)
- .describe("2-3 sentences: what it means and when it applies."),
- confidence: z
- .number()
- .min(0)
- .max(1)
- .describe("How explicitly the user stated this (1 = explicit)."),
- decision: decisionSchema,
-}
-
-const stanceEntrySchema = z.preprocess((value) => {
- if (!value || typeof value !== "object" || Array.isArray(value)) return value
- const record = value as Record
- // Models sometimes emit "name" for a stance; the contract field is "statement".
- if (
- (typeof record.statement !== "string" || record.statement.length === 0) &&
- typeof record.name === "string" &&
- record.name.length > 0
- ) {
- const { name, ...rest } = record
- return { ...rest, statement: name }
- }
- return value
-}, stancePayloadSchema.extend(memorySidecarFields))
-
-const entityEntrySchema = z.preprocess((value) => {
- if (!value || typeof value !== "object" || Array.isArray(value)) return value
- const record = value as Record
- // Keep provenance reason required; if the model omitted it, fall back to L0.
- if (
- (typeof record.reason !== "string" || record.reason.length === 0) &&
- typeof record.abstractL0 === "string" &&
- record.abstractL0.length > 0
- ) {
- return { ...record, reason: record.abstractL0 }
- }
- return value
-}, entityOfInterestPayloadSchema.extend(memorySidecarFields))
-
-export const memoryOperationsSchema = z.object({
- indicatorPrefs: z
- .array(indicatorPreferencePayloadSchema.extend(memorySidecarFields))
- .default([]),
- stances: z.array(stanceEntrySchema).default([]),
- decisionRules: z
- .array(decisionRulePayloadSchema.extend(memorySidecarFields))
- .default([]),
- entities: z.array(entityEntrySchema).default([]),
-})
-
-export type MemoryOperations = z.infer
-
-export type ExistingMemoryContextItem = {
- readonly id: string
- readonly kind: FluidMemoryKind
- readonly abstractL0: string
- readonly payloadSummary: string
-}
-
-/** Structural output shape only — no domain content. */
-const OUTPUT_SCHEMA_BLOCK = `{
- "indicatorPrefs": [{
- "name": "string",
- "aliases": ["string"],
- "definition": "string",
- "polarity": "higher_better|lower_better|context",
- "importance": "core|secondary",
- "formulaHint": "string (optional — omit if none)",
- "abstractL0": "string",
- "overviewL1": "string",
- "confidence": 0.0,
- "decision": { "op": "create|skip|merge|deprecate", "targetItemId": "id for merge/deprecate only", "reason": "string optional" }
- }],
- "stances": [{
- "statement": "string (the stance text; do not use a name field)",
- "scope": "string",
- "rationale": "string",
- "abstractL0": "string",
- "overviewL1": "string",
- "confidence": 0.0,
- "decision": { "op": "create|skip|merge|deprecate", "targetItemId": "id for merge/deprecate only", "reason": "string optional" }
- }],
- "decisionRules": [{
- "when": "string",
- "then": "string",
- "priority": "high|medium|low",
- "rationale": "string",
- "abstractL0": "string",
- "overviewL1": "string",
- "confidence": 0.0,
- "decision": { "op": "create|skip|merge|deprecate", "targetItemId": "id for merge/deprecate only", "reason": "string optional" }
- }],
- "entities": [{
- "name": "string",
- "ticker": "string optional",
- "aliases": ["string"],
- "knowhereDocumentIds": ["only ids listed under REFERENCED DOCUMENT IDS"],
- "reason": "string",
- "abstractL0": "string",
- "overviewL1": "string",
- "confidence": 0.0,
- "decision": { "op": "create|skip|merge|deprecate", "targetItemId": "id for merge/deprecate only", "reason": "string optional" }
- }]
-}`
-
-/**
- * Illustrative only — kept separate from the main instructions so the model
- * does not treat these domain phrases as required vocabulary.
- * Finance is the first vertical; add other industry blocks here later if needed.
- */
-const ILLUSTRATIVE_EXAMPLES_BLOCK = `## Illustrative examples (finance vertical — not exhaustive, not required vocabulary)
-
-These show shape and judgement only. Extract whatever the user actually said;
-do not force the conversation into this domain or these metric names.
-
-- indicatorPref: user says a named metric they repeatedly use to judge quality
- (shape: name + short definition + polarity + importance). Same idea applies
- outside finance (any recurring evaluation metric).
-- stance: user states a durable judgement frame that changes how evidence is
- weighted (e.g. long-horizon vs short-horizon).
-- decisionRule: user states a reusable when → then discipline over their metrics.
-- entity: user says they actively track a named company/issuer and why.
-- skip: a one-off factual question about a page/number in a document, small talk,
- or an assistant suggestion the user did not endorse.`
-
-/** Domain-agnostic extraction instructions. */
-const MAIN_INSTRUCTIONS_BLOCK = `You maintain a user's FLUID MEMORY: durable insights about how this user thinks, extracted from their conversation with an AI analyst.
-
-Document facts live elsewhere (crystal memory). Never extract document facts, retrieved numbers, or page content as fluid memory.
-
-## What to extract
-
-Extract ONLY these four kinds, and ONLY when the turn gives real evidence from the USER:
-
-- indicatorPrefs — recurring metrics or criteria the user uses to evaluate things.
- Fields: name, aliases, definition, polarity (higher_better | lower_better | context),
- importance (core | secondary), optional formulaHint.
-- stances — durable positions that shape how the user weighs evidence.
- Fields: statement (required; do not invent a "name" field), scope, rationale.
-- decisionRules — reusable when → then disciplines the user stated or clearly endorsed.
- Fields: when, then, priority (high | medium | low), rationale.
-- entities — named subjects the user is actively tracking.
- Fields: name, optional ticker, aliases, reason (required), knowhereDocumentIds
- (only from REFERENCED DOCUMENT IDS below; never invent ids).
-
-## Language
-
-- Keep this instruction set and enum/field names in English.
-- Write every free-text value (name, definition, statement, when/then, reason,
- abstractL0, overviewL1, aliases the user used, etc.) in the same language the
- USER wrote in this turn. Do not translate the user's terms into English unless
- the user themselves used English.
-
-## Decision rules
-
-- Extract only durable, reusable insights about the USER.
-- Skip one-off questions, document facts, small talk, and assistant claims the user did not endorse.
-- For every candidate, choose exactly one op against EXISTING MEMORIES:
- - create — genuinely new
- - skip — already covered, or too weak/ephemeral
- - merge — same insight refined; emit the full merged fields and set targetItemId
- - deprecate — user explicitly reversed a stored item; set targetItemId
-- Be conservative: prefer create over merge when overlap is only partial; deprecate only on clear contradiction.
-- Prefer one record per insight. If a preference already encodes how a metric should be read, do not also invent a near-duplicate decisionRule unless the user stated an explicit when → then action.
-- abstractL0: one line. overviewL1: 2–3 sentences. confidence=1 only when the user stated it explicitly.
-- Omit optional fields instead of setting them to null.
-- If nothing is worth remembering, return all four arrays empty.`
-
-export function buildMemoryExtractionPrompt(input: {
- readonly userText: string
- readonly assistantText: string
- readonly referencedDocumentIds: readonly string[]
- readonly existingItems: readonly ExistingMemoryContextItem[]
-}): string {
- const existingBlock =
- input.existingItems.length === 0
- ? "(no existing memories yet)"
- : input.existingItems
- .map(
- (item) =>
- `- [${item.kind}] id=${item.id} :: ${item.abstractL0} :: ${item.payloadSummary}`,
- )
- .join("\n")
-
- const documentsBlock =
- input.referencedDocumentIds.length === 0
- ? "(no documents referenced in this turn)"
- : input.referencedDocumentIds.join(", ")
-
- return `${MAIN_INSTRUCTIONS_BLOCK}
-
-${ILLUSTRATIVE_EXAMPLES_BLOCK}
-
-## Output JSON schema (follow exactly; do not invent fields)
-
-${OUTPUT_SCHEMA_BLOCK}
-
-## EXISTING MEMORIES
-
-${existingBlock}
-
-## REFERENCED DOCUMENT IDS
-
-${documentsBlock}
-
-## CONVERSATION TURN
-
-[user]
-${input.userText}
-
-[assistant]
-${input.assistantText}`
-}
-
-export function summarizePayloadForContext(
- kind: FluidMemoryKind,
- payload: unknown,
-): string {
- if (!payload || typeof payload !== "object") return ""
- const record = payload as Record
- switch (kind) {
- case "indicator_pref":
- return [record.name, record.definition]
- .filter((part) => typeof part === "string" && part.length > 0)
- .join(" — ")
- case "stance":
- return typeof record.statement === "string" ? record.statement : ""
- case "decision_rule":
- return [record.when, record.then]
- .filter((part) => typeof part === "string" && part.length > 0)
- .join(" => ")
- case "entity_of_interest":
- return [record.name, record.ticker]
- .filter((part) => typeof part === "string" && part.length > 0)
- .join(" ")
- }
-}
diff --git a/src/domains/memory/repository.ts b/src/domains/memory/repository.ts
deleted file mode 100644
index f7f6b794..00000000
--- a/src/domains/memory/repository.ts
+++ /dev/null
@@ -1,254 +0,0 @@
-import "server-only"
-
-import { and, eq, inArray, sql } from "drizzle-orm"
-import { Effect } from "effect"
-
-import { toDiffOperation, type ResolvedMemoryOperation } from "./resolve-operations"
-import { buildMemoryItemTokens } from "./search-index"
-import type {
- FluidMemoryKind,
- FluidMemoryPayload,
- MemoryDiffOperation,
-} from "./types"
-import { DbClient } from "@/infrastructure/db"
-import {
- fluidMemoryItems,
- fluidMemoryTokens,
- memoryDiffs,
- type FluidMemoryItem,
- type NewFluidMemoryToken,
-} from "@/infrastructure/db/schema"
-
-type MemoryRepository = {
- readonly findDedupCandidatesEffect: (
- workspaceId: string,
- kind: FluidMemoryKind,
- tokens: readonly string[],
- limit: number,
- ) => Effect.Effect
- readonly applyOperationsEffect: (
- workspaceId: string,
- sourceMessageId: string | null,
- operations: readonly ResolvedMemoryOperation[],
- ) => Effect.Effect
-}
-
-type RawRowsResult
= readonly Row[] | { readonly rows: readonly Row[] }
-
-/**
- * Retrieve the most lexically-similar active items of one kind, ranked by
- * idf-weighted token overlap computed entirely in SQL. Common tokens (high
- * document frequency within this workspace + kind) are down-weighted so a
- * shared rare term outranks several shared filler characters.
- *
- * Token rows only exist for active items (see schema invariant), so no
- * status filter is needed here.
- */
-const findDedupCandidatesEffect: MemoryRepository["findDedupCandidatesEffect"] =
- (workspaceId, kind, tokens, limit) =>
- Effect.gen(function* () {
- const db = yield* DbClient
- if (tokens.length === 0 || limit <= 0) return []
-
- const tokenList = sql.join(
- tokens.map((token) => sql`${token}`),
- sql`, `,
- )
-
- const scored = yield* Effect.promise(() =>
- db.execute<{ itemId: string }>(sql`
- SELECT t.item_id AS "itemId", SUM(t.frequency::float8 / df.df) AS score
- FROM fluid_memory_tokens t
- JOIN (
- SELECT token, COUNT(DISTINCT item_id)::float8 AS df
- FROM fluid_memory_tokens
- WHERE workspace_id = ${workspaceId}::uuid
- AND kind = ${kind}
- AND token IN (${tokenList})
- GROUP BY token
- ) df ON df.token = t.token
- WHERE t.workspace_id = ${workspaceId}::uuid
- AND t.kind = ${kind}
- AND t.token IN (${tokenList})
- GROUP BY t.item_id
- ORDER BY score DESC
- LIMIT ${limit}
- `),
- )
-
- const orderedIds = getRawRows(scored).map((row) => row.itemId)
- if (orderedIds.length === 0) return []
-
- const items = yield* Effect.promise(() =>
- db
- .select()
- .from(fluidMemoryItems)
- .where(inArray(fluidMemoryItems.id, orderedIds)),
- )
- const byId = new Map(items.map((item) => [item.id, item] as const))
- return orderedIds.flatMap((id) => {
- const item = byId.get(id)
- return item ? [item] : []
- })
- })
-
-const applyOperationsEffect: MemoryRepository["applyOperationsEffect"] = (
- workspaceId,
- sourceMessageId,
- operations,
-) =>
- Effect.gen(function* () {
- const db = yield* DbClient
- return yield* Effect.promise(() =>
- db.transaction(async (tx) => {
- const diffOperations: MemoryDiffOperation[] = []
-
- for (const operation of operations) {
- switch (operation.op) {
- case "create": {
- const [inserted] = await tx
- .insert(fluidMemoryItems)
- .values({
- workspaceId,
- kind: operation.kind,
- payload: operation.payload,
- abstractL0: operation.abstractL0,
- overviewL1: operation.overviewL1,
- sourceMessageId,
- confidence: operation.confidence,
- status: "active",
- })
- .returning()
- if (inserted?.id) {
- const tokenRows = tokenRowsFor(
- workspaceId,
- inserted.id,
- operation.kind,
- operation.payload,
- )
- if (tokenRows.length > 0) {
- await tx.insert(fluidMemoryTokens).values(tokenRows)
- }
- }
- diffOperations.push(toDiffOperation(operation, inserted?.id))
- break
- }
- case "merge": {
- const [updated] = await tx
- .update(fluidMemoryItems)
- .set({
- payload: operation.payload,
- abstractL0: operation.abstractL0,
- overviewL1: operation.overviewL1,
- confidence: operation.confidence,
- sourceMessageId,
- version: sql`${fluidMemoryItems.version} + 1`,
- updatedAt: sql`now()`,
- })
- .where(
- and(
- eq(fluidMemoryItems.id, operation.targetItemId),
- eq(fluidMemoryItems.status, "active"),
- ),
- )
- .returning({ id: fluidMemoryItems.id })
- if (updated) {
- await tx
- .delete(fluidMemoryTokens)
- .where(eq(fluidMemoryTokens.itemId, operation.targetItemId))
- const tokenRows = tokenRowsFor(
- workspaceId,
- operation.targetItemId,
- operation.kind,
- operation.payload,
- )
- if (tokenRows.length > 0) {
- await tx.insert(fluidMemoryTokens).values(tokenRows)
- }
- }
- diffOperations.push(
- updated
- ? toDiffOperation(operation)
- : {
- op: "skip",
- kind: operation.kind,
- summary: operation.summary,
- reason: "merge target no longer active",
- },
- )
- break
- }
- case "deprecate": {
- const [updated] = await tx
- .update(fluidMemoryItems)
- .set({
- status: "deprecated",
- updatedAt: sql`now()`,
- })
- .where(
- and(
- eq(fluidMemoryItems.id, operation.targetItemId),
- eq(fluidMemoryItems.status, "active"),
- ),
- )
- .returning({ id: fluidMemoryItems.id })
- if (updated) {
- await tx
- .delete(fluidMemoryTokens)
- .where(eq(fluidMemoryTokens.itemId, operation.targetItemId))
- }
- diffOperations.push(
- updated
- ? toDiffOperation(operation)
- : {
- op: "skip",
- kind: operation.kind,
- summary: operation.summary,
- reason: "deprecate target no longer active",
- },
- )
- break
- }
- case "skip":
- diffOperations.push(toDiffOperation(operation))
- break
- }
- }
-
- if (diffOperations.length > 0) {
- await tx.insert(memoryDiffs).values({
- workspaceId,
- sourceMessageId,
- operations: [...diffOperations],
- })
- }
-
- return diffOperations
- }),
- )
- })
-
-export const memoryRepository: MemoryRepository = {
- findDedupCandidatesEffect,
- applyOperationsEffect,
-}
-
-function tokenRowsFor(
- workspaceId: string,
- itemId: string,
- kind: FluidMemoryKind,
- payload: FluidMemoryPayload,
-): NewFluidMemoryToken[] {
- return buildMemoryItemTokens(kind, payload).map((token) => ({
- workspaceId,
- itemId,
- kind,
- token: token.token,
- frequency: token.frequency,
- }))
-}
-
-function getRawRows(value: RawRowsResult): readonly Row[] {
- if (Array.isArray(value)) return value
- return (value as { readonly rows: readonly Row[] }).rows
-}
diff --git a/src/domains/memory/resolve-operations.test.ts b/src/domains/memory/resolve-operations.test.ts
deleted file mode 100644
index 9764ccfd..00000000
--- a/src/domains/memory/resolve-operations.test.ts
+++ /dev/null
@@ -1,378 +0,0 @@
-import { describe, expect, it } from "vitest"
-
-import type { MemoryOperations } from "./prompts"
-import {
- resolveMemoryOperations,
- toDiffOperation,
-} from "./resolve-operations"
-
-const existingItems = [
- {
- id: "item-1",
- kind: "indicator_pref",
- status: "active",
- payload: {
- name: "毛利率",
- aliases: ["gross margin"],
- definition: "毛利占营收的比例",
- polarity: "higher_better",
- importance: "core",
- },
- },
- { id: "item-2", kind: "stance", status: "active" },
- { id: "item-3", kind: "stance", status: "deprecated" },
- {
- id: "item-4",
- kind: "entity_of_interest",
- status: "active",
- payload: {
- name: "英伟达",
- ticker: "NVDA",
- aliases: ["NVIDIA"],
- knowhereDocumentIds: ["doc-earlier"],
- reason: "用户持续跟踪",
- },
- },
-] as const
-
-function makeOperations(
- overrides: Partial = {},
-): MemoryOperations {
- return {
- indicatorPrefs: [],
- stances: [],
- decisionRules: [],
- entities: [],
- ...overrides,
- }
-}
-
-function makeIndicatorEntry(decision: {
- op: "create" | "skip" | "merge" | "deprecate"
- targetItemId?: string
- reason?: string
-}) {
- return {
- name: "毛利率",
- aliases: ["gross margin"],
- definition: "毛利占营收的比例",
- polarity: "higher_better" as const,
- importance: "core" as const,
- abstractL0: "用户看重毛利率",
- overviewL1: "用户在分析公司时首先看毛利率。",
- confidence: 0.9,
- decision,
- }
-}
-
-describe("resolveMemoryOperations", () => {
- it("passes create through and ignores a stray targetItemId", () => {
- const resolved = resolveMemoryOperations({
- operations: makeOperations({
- indicatorPrefs: [
- makeIndicatorEntry({ op: "create", targetItemId: "item-1" }),
- ],
- }),
- existingItems,
- referencedDocumentIds: [],
- })
-
- expect(resolved).toHaveLength(1)
- expect(resolved[0]).toMatchObject({
- op: "create",
- kind: "indicator_pref",
- payload: {
- name: "毛利率",
- aliases: ["gross margin"],
- polarity: "higher_better",
- },
- })
- expect(resolved[0]).not.toHaveProperty("targetItemId")
- })
-
- it("merges into an existing active item of the same kind", () => {
- const resolved = resolveMemoryOperations({
- operations: makeOperations({
- indicatorPrefs: [
- makeIndicatorEntry({ op: "merge", targetItemId: "item-1" }),
- ],
- }),
- existingItems,
- referencedDocumentIds: [],
- })
-
- expect(resolved[0]).toMatchObject({
- op: "merge",
- kind: "indicator_pref",
- targetItemId: "item-1",
- })
- })
-
- it("downgrades merge to skip when the target is missing", () => {
- const resolved = resolveMemoryOperations({
- operations: makeOperations({
- indicatorPrefs: [
- makeIndicatorEntry({ op: "merge", targetItemId: "item-999" }),
- ],
- }),
- existingItems,
- referencedDocumentIds: [],
- })
-
- expect(resolved[0]).toMatchObject({
- op: "skip",
- kind: "indicator_pref",
- })
- expect(resolved[0]).not.toHaveProperty("targetItemId")
- })
-
- it("downgrades merge to skip on kind mismatch", () => {
- const resolved = resolveMemoryOperations({
- operations: makeOperations({
- indicatorPrefs: [
- makeIndicatorEntry({ op: "merge", targetItemId: "item-2" }),
- ],
- }),
- existingItems,
- referencedDocumentIds: [],
- })
-
- expect(resolved[0]?.op).toBe("skip")
- })
-
- it("downgrades merge to skip when the target is already deprecated", () => {
- const resolved = resolveMemoryOperations({
- operations: makeOperations({
- stances: [
- {
- statement: "长期持有,忽略短期波动",
- scope: "投资",
- rationale: "用户做长期投资",
- abstractL0: "长期投资立场",
- overviewL1: "用户强调长期持有。",
- confidence: 1,
- decision: { op: "merge", targetItemId: "item-3" },
- },
- ],
- }),
- existingItems,
- referencedDocumentIds: [],
- })
-
- expect(resolved[0]?.op).toBe("skip")
- })
-
- it("keeps deprecate for an active target", () => {
- const resolved = resolveMemoryOperations({
- operations: makeOperations({
- stances: [
- {
- statement: "美联储短期观点不重要",
- scope: "宏观",
- rationale: "长期投资",
- abstractL0: "不看美联储短期观点",
- overviewL1: "用户认为美联储短期观点权重低。",
- confidence: 1,
- decision: {
- op: "deprecate",
- targetItemId: "item-2",
- reason: "用户改口",
- },
- },
- ],
- }),
- existingItems,
- referencedDocumentIds: [],
- })
-
- expect(resolved[0]).toMatchObject({
- op: "deprecate",
- targetItemId: "item-2",
- reason: "用户改口",
- })
- })
-
- it("filters entity document ids to the turn's referenced documents", () => {
- const resolved = resolveMemoryOperations({
- operations: makeOperations({
- entities: [
- {
- name: "英伟达",
- ticker: "NVDA",
- aliases: ["NVIDIA"],
- knowhereDocumentIds: ["doc-real", "doc-hallucinated"],
- reason: "用户持续跟踪",
- abstractL0: "用户关注英伟达",
- overviewL1: "用户多次询问英伟达财报。",
- confidence: 0.8,
- decision: { op: "create" },
- },
- ],
- }),
- existingItems,
- referencedDocumentIds: ["doc-real"],
- })
-
- expect(resolved[0]).toMatchObject({
- op: "create",
- kind: "entity_of_interest",
- payload: {
- name: "英伟达",
- knowhereDocumentIds: ["doc-real"],
- },
- })
- })
-
- it("unions stored document ids when merging an entity", () => {
- const resolved = resolveMemoryOperations({
- operations: makeOperations({
- entities: [
- {
- name: "英伟达",
- ticker: "NVDA",
- aliases: ["NVDA Corp"],
- knowhereDocumentIds: ["doc-this-turn"],
- reason: "用户持续跟踪",
- abstractL0: "用户关注英伟达",
- overviewL1: "用户多次询问英伟达财报。",
- confidence: 0.9,
- decision: { op: "merge", targetItemId: "item-4" },
- },
- ],
- }),
- existingItems,
- referencedDocumentIds: ["doc-this-turn"],
- })
-
- expect(resolved[0]).toMatchObject({
- op: "merge",
- targetItemId: "item-4",
- payload: {
- aliases: ["NVIDIA", "NVDA Corp"],
- knowhereDocumentIds: ["doc-earlier", "doc-this-turn"],
- },
- })
- })
-
- it("unions aliases when merging an indicator preference", () => {
- const resolved = resolveMemoryOperations({
- operations: makeOperations({
- indicatorPrefs: [
- {
- name: "毛利率",
- aliases: ["同比毛利率"],
- definition: "毛利占营收的比例,也看同比",
- polarity: "higher_better" as const,
- importance: "core" as const,
- abstractL0: "毛利率也看同比",
- overviewL1: "用户补充了同比视角。",
- confidence: 1,
- decision: { op: "merge", targetItemId: "item-1" },
- },
- ],
- }),
- existingItems,
- referencedDocumentIds: [],
- })
-
- expect(resolved[0]).toMatchObject({
- op: "merge",
- targetItemId: "item-1",
- payload: {
- aliases: ["gross margin", "同比毛利率"],
- },
- })
- })
-
- it("skips create when the payload has no searchable tokens", () => {
- const resolved = resolveMemoryOperations({
- operations: makeOperations({
- indicatorPrefs: [
- {
- name: "!!!",
- aliases: [],
- definition: "???",
- polarity: "context" as const,
- importance: "secondary" as const,
- abstractL0: "无效符号",
- overviewL1: "无法检索。",
- confidence: 0.1,
- decision: { op: "create" },
- },
- ],
- }),
- existingItems,
- referencedDocumentIds: [],
- })
-
- expect(resolved[0]).toMatchObject({
- op: "skip",
- kind: "indicator_pref",
- reason: "payload has no searchable tokens",
- })
- })
-
- it("skips merge when the merged payload has no searchable tokens", () => {
- const resolved = resolveMemoryOperations({
- operations: makeOperations({
- stances: [
- {
- statement: "!!!",
- scope: "???",
- rationale: "...",
- abstractL0: "无效符号",
- overviewL1: "无法检索。",
- confidence: 0.1,
- decision: { op: "merge", targetItemId: "item-2" },
- },
- ],
- }),
- existingItems,
- referencedDocumentIds: [],
- })
-
- expect(resolved[0]).toMatchObject({
- op: "skip",
- kind: "stance",
- reason: "merged payload has no searchable tokens",
- })
- })
-})
-
-describe("toDiffOperation", () => {
- it("records create with the inserted item id", () => {
- const resolved = resolveMemoryOperations({
- operations: makeOperations({
- indicatorPrefs: [makeIndicatorEntry({ op: "create" })],
- }),
- existingItems,
- referencedDocumentIds: [],
- })
-
- expect(toDiffOperation(resolved[0]!, "new-id")).toEqual({
- op: "create",
- kind: "indicator_pref",
- summary: "用户看重毛利率",
- itemId: "new-id",
- })
- })
-
- it("records merge/deprecate with their target item id", () => {
- const resolved = resolveMemoryOperations({
- operations: makeOperations({
- indicatorPrefs: [
- makeIndicatorEntry({ op: "merge", targetItemId: "item-1" }),
- ],
- }),
- existingItems,
- referencedDocumentIds: [],
- })
-
- expect(toDiffOperation(resolved[0]!)).toEqual({
- op: "merge",
- kind: "indicator_pref",
- summary: "用户看重毛利率",
- itemId: "item-1",
- })
- })
-})
diff --git a/src/domains/memory/resolve-operations.ts b/src/domains/memory/resolve-operations.ts
deleted file mode 100644
index ee1e6a42..00000000
--- a/src/domains/memory/resolve-operations.ts
+++ /dev/null
@@ -1,314 +0,0 @@
-import { buildMemoryItemTokens } from "./search-index"
-import type { MemoryOperations } from "./prompts"
-import {
- parseFluidMemoryPayload,
- type FluidMemoryKind,
- type FluidMemoryPayload,
- type MemoryDiffOperation,
-} from "./types"
-
-/**
- * Pure normalization from raw LLM operations to repository-ready
- * operations. The LLM output already passed zod validation; this layer
- * enforces the invariants the schema cannot express:
- * - merge/deprecate must target an existing active item of the same kind
- * (otherwise downgraded to skip — conservative, never fabricates)
- * - entity knowhereDocumentIds are intersected with the document ids
- * actually referenced in the turn (the model cannot invent provenance)
- * - create ignores any targetItemId the model may have emitted
- * - create/merge payloads must yield at least one lexical token, otherwise
- * the item could never be retrieved for later dedup
- * - merge unions aliases (and entity document ids) with the target so
- * prior search terms / provenance are not wiped by a partial rewrite
- */
-
-export type ResolvedMemoryOperation =
- | {
- readonly op: "create"
- readonly kind: FluidMemoryKind
- readonly payload: FluidMemoryPayload
- readonly abstractL0: string
- readonly overviewL1: string
- readonly confidence: number
- readonly summary: string
- readonly reason?: string
- }
- | {
- readonly op: "skip"
- readonly kind: FluidMemoryKind
- readonly summary: string
- readonly reason?: string
- }
- | {
- readonly op: "merge"
- readonly kind: FluidMemoryKind
- readonly targetItemId: string
- readonly payload: FluidMemoryPayload
- readonly abstractL0: string
- readonly overviewL1: string
- readonly confidence: number
- readonly summary: string
- readonly reason?: string
- }
- | {
- readonly op: "deprecate"
- readonly kind: FluidMemoryKind
- readonly targetItemId: string
- readonly summary: string
- readonly reason?: string
- }
-
-export type ExistingMemoryItemRef = {
- readonly id: string
- readonly kind: string
- readonly status: string
- readonly payload?: unknown
-}
-
-type CandidateEntry = {
- readonly abstractL0: string
- readonly overviewL1: string
- readonly confidence: number
- readonly decision: {
- readonly op: "create" | "skip" | "merge" | "deprecate"
- readonly targetItemId?: string
- readonly reason?: string
- }
-}
-
-const kindToArrayKey = {
- indicator_pref: "indicatorPrefs",
- stance: "stances",
- decision_rule: "decisionRules",
- entity_of_interest: "entities",
-} as const
-
-export function resolveMemoryOperations(input: {
- readonly operations: MemoryOperations
- readonly existingItems: readonly ExistingMemoryItemRef[]
- readonly referencedDocumentIds: readonly string[]
-}): ResolvedMemoryOperation[] {
- const activeById = new Map(
- input.existingItems
- .filter((item) => item.status === "active")
- .map((item) => [item.id, item] as const),
- )
- const allowedDocumentIds = new Set(input.referencedDocumentIds)
-
- const resolved: ResolvedMemoryOperation[] = []
-
- for (const kind of Object.keys(kindToArrayKey) as FluidMemoryKind[]) {
- const entries = input.operations[kindToArrayKey[kind]] as readonly (CandidateEntry &
- Record)[]
-
- for (const entry of entries) {
- const summary = entry.abstractL0
- const reason = entry.decision.reason
-
- if (entry.decision.op === "skip") {
- resolved.push({ op: "skip", kind, summary, ...(reason ? { reason } : {}) })
- continue
- }
-
- if (entry.decision.op === "merge" || entry.decision.op === "deprecate") {
- const targetId = entry.decision.targetItemId
- const target = targetId ? activeById.get(targetId) : undefined
- if (!target || target.kind !== kind) {
- resolved.push({
- op: "skip",
- kind,
- summary,
- reason: `${entry.decision.op} target missing, inactive, or kind mismatch`,
- })
- continue
- }
- if (entry.decision.op === "deprecate") {
- resolved.push({
- op: "deprecate",
- kind,
- targetItemId: target.id,
- summary,
- ...(reason ? { reason } : {}),
- })
- continue
- }
- const mergePayload = toPayload(kind, entry, allowedDocumentIds)
- if (!mergePayload) {
- resolved.push({
- op: "skip",
- kind,
- summary,
- reason: "merged payload failed validation",
- })
- continue
- }
- const preserved = preserveFieldsOnMerge(
- kind,
- mergePayload,
- target.payload,
- )
- if (!isIndexable(kind, preserved)) {
- resolved.push({
- op: "skip",
- kind,
- summary,
- reason: "merged payload has no searchable tokens",
- })
- continue
- }
- resolved.push({
- op: "merge",
- kind,
- targetItemId: target.id,
- payload: preserved,
- abstractL0: entry.abstractL0,
- overviewL1: entry.overviewL1,
- confidence: entry.confidence,
- summary,
- ...(reason ? { reason } : {}),
- })
- continue
- }
-
- const createPayload = toPayload(kind, entry, allowedDocumentIds)
- if (!createPayload) {
- resolved.push({
- op: "skip",
- kind,
- summary,
- reason: "payload failed validation",
- })
- continue
- }
- if (!isIndexable(kind, createPayload)) {
- resolved.push({
- op: "skip",
- kind,
- summary,
- reason: "payload has no searchable tokens",
- })
- continue
- }
- resolved.push({
- op: "create",
- kind,
- payload: createPayload,
- abstractL0: entry.abstractL0,
- overviewL1: entry.overviewL1,
- confidence: entry.confidence,
- summary,
- ...(reason ? { reason } : {}),
- })
- }
- }
-
- return resolved
-}
-
-function toPayload(
- kind: FluidMemoryKind,
- entry: Record,
- allowedDocumentIds: ReadonlySet,
-): FluidMemoryPayload | null {
- const candidate: Record = {
- name: entry.name,
- aliases: entry.aliases,
- definition: entry.definition,
- polarity: entry.polarity,
- importance: entry.importance,
- formulaHint: entry.formulaHint,
- statement: entry.statement,
- scope: entry.scope,
- rationale: entry.rationale,
- when: entry.when,
- then: entry.then,
- priority: entry.priority,
- ticker: entry.ticker,
- reason: entry.reason,
- knowhereDocumentIds: Array.isArray(entry.knowhereDocumentIds)
- ? entry.knowhereDocumentIds.filter(
- (id): id is string =>
- typeof id === "string" && allowedDocumentIds.has(id),
- )
- : [],
- }
- return parseFluidMemoryPayload(kind, candidate)
-}
-
-/** Reject payloads that could never be found again by the token index. */
-function isIndexable(kind: FluidMemoryKind, payload: FluidMemoryPayload): boolean {
- return buildMemoryItemTokens(kind, payload).length > 0
-}
-
-/**
- * Merge replaces the stored payload, but the model only sees this turn.
- * Union aliases (and entity document ids) with the target so earlier search
- * terms / provenance survive a partial rewrite.
- */
-function preserveFieldsOnMerge(
- kind: FluidMemoryKind,
- mergedPayload: FluidMemoryPayload,
- existingPayload: unknown,
-): FluidMemoryPayload {
- const existing = parseFluidMemoryPayload(kind, existingPayload)
- if (!existing) return mergedPayload
-
- if (
- kind === "indicator_pref" &&
- "aliases" in mergedPayload &&
- "aliases" in existing
- ) {
- return {
- ...mergedPayload,
- aliases: unionStrings(existing.aliases, mergedPayload.aliases),
- }
- }
-
- if (
- kind === "entity_of_interest" &&
- "aliases" in mergedPayload &&
- "aliases" in existing &&
- "knowhereDocumentIds" in mergedPayload &&
- "knowhereDocumentIds" in existing
- ) {
- return {
- ...mergedPayload,
- aliases: unionStrings(existing.aliases, mergedPayload.aliases),
- knowhereDocumentIds: unionStrings(
- existing.knowhereDocumentIds,
- mergedPayload.knowhereDocumentIds,
- ),
- }
- }
-
- return mergedPayload
-}
-
-function unionStrings(
- left: readonly string[],
- right: readonly string[],
-): string[] {
- return [...new Set([...left, ...right])]
-}
-
-/** Diff-audit view of a resolved operation (itemId filled after write). */
-export function toDiffOperation(
- operation: ResolvedMemoryOperation,
- itemId?: string,
-): MemoryDiffOperation {
- const base = {
- kind: operation.kind,
- summary: operation.summary,
- ...(operation.reason ? { reason: operation.reason } : {}),
- }
- switch (operation.op) {
- case "create":
- return { op: "create", ...base, ...(itemId ? { itemId } : {}) }
- case "merge":
- return { op: "merge", ...base, itemId: operation.targetItemId }
- case "deprecate":
- return { op: "deprecate", ...base, itemId: operation.targetItemId }
- case "skip":
- return { op: "skip", ...base }
- }
-}
diff --git a/src/domains/memory/search-index.test.ts b/src/domains/memory/search-index.test.ts
deleted file mode 100644
index cbf5d0b3..00000000
--- a/src/domains/memory/search-index.test.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import { describe, expect, it } from "vitest"
-
-import {
- buildMemoryItemTokens,
- buildMemorySearchText,
- tokenizeMemoryText,
-} from "./search-index"
-
-describe("tokenizeMemoryText", () => {
- it("splits CJK into single characters and latin into words", () => {
- expect(tokenizeMemoryText("毛利率 PE gross_margin")).toEqual([
- { token: "毛", frequency: 1 },
- { token: "利", frequency: 1 },
- { token: "率", frequency: 1 },
- { token: "pe", frequency: 1 },
- { token: "gross_margin", frequency: 1 },
- ])
- })
-
- it("counts repeated tokens", () => {
- expect(tokenizeMemoryText("PE pe Pe")).toEqual([
- { token: "pe", frequency: 3 },
- ])
- })
-
- it("returns empty for whitespace-only input", () => {
- expect(tokenizeMemoryText(" ")).toEqual([])
- })
-})
-
-describe("buildMemorySearchText", () => {
- it("indexes indicator name, aliases, and definition", () => {
- expect(
- buildMemorySearchText("indicator_pref", {
- name: "毛利率",
- aliases: ["gross margin"],
- definition: "毛利除以营收",
- polarity: "higher_better",
- importance: "core",
- }),
- ).toBe("毛利率 gross margin 毛利除以营收")
- })
-
- it("indexes entity name, aliases, and ticker without reason", () => {
- expect(
- buildMemorySearchText("entity_of_interest", {
- name: "英伟达",
- aliases: ["NVIDIA"],
- ticker: "NVDA",
- knowhereDocumentIds: ["doc-1"],
- reason: "一直在跟踪",
- }),
- ).toBe("英伟达 NVIDIA NVDA")
- })
-
- it("indexes stance statement and scope", () => {
- expect(
- buildMemorySearchText("stance", {
- statement: "做长期投资",
- scope: "宏观短期观点",
- rationale: "美联储短期说法不重要",
- }),
- ).toBe("做长期投资 宏观短期观点")
- })
-
- it("indexes decision rule when and then", () => {
- expect(
- buildMemorySearchText("decision_rule", {
- when: "毛利率连续两季下滑",
- then: "减仓观望",
- priority: "high",
- rationale: "用户明确说过",
- }),
- ).toBe("毛利率连续两季下滑 减仓观望")
- })
-})
-
-describe("buildMemoryItemTokens", () => {
- it("tokenizes the search text of an item", () => {
- const tokens = buildMemoryItemTokens("indicator_pref", {
- name: "PE",
- aliases: [],
- definition: "市盈率",
- polarity: "context",
- importance: "secondary",
- })
- expect(tokens.map((token) => token.token)).toEqual([
- "pe",
- "市",
- "盈",
- "率",
- ])
- })
-})
diff --git a/src/domains/memory/search-index.ts b/src/domains/memory/search-index.ts
deleted file mode 100644
index 84a53e9b..00000000
--- a/src/domains/memory/search-index.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import type {
- DecisionRulePayload,
- EntityOfInterestPayload,
- FluidMemoryKind,
- FluidMemoryPayload,
- IndicatorPreferencePayload,
- StancePayload,
-} from "./types"
-
-/**
- * Lexical search-index helpers for fluid memory dedup retrieval.
- *
- * Pure and dependency-free so both the write path (indexing an item) and the
- * read path (turning a turn into a query) share one tokenizer. Tokenization
- * mirrors Knowhere map-nav: lowercase, then emit single CJK characters and
- * `[a-z0-9_]+` runs. This handles Chinese (no whitespace segmentation) and
- * Latin/alphanumeric terms without any Postgres extension.
- */
-
-export type MemoryToken = {
- readonly token: string
- readonly frequency: number
-}
-
-// Single CJK char OR a run of latin letters / digits / underscore.
-const TOKEN_PATTERN =
- /[a-z0-9_]+|[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/g
-
-/**
- * Build the text that represents an item for lexical matching. Only the
- * fields a user would phrase a query against are included (names, aliases,
- * short definitions), not provenance or bookkeeping fields.
- */
-export function buildMemorySearchText(
- kind: FluidMemoryKind,
- payload: FluidMemoryPayload,
-): string {
- return collectSearchParts(kind, payload)
- .filter((part) => part.length > 0)
- .join(" ")
-}
-
-/** Tokenize free text into deduped tokens with occurrence counts. */
-export function tokenizeMemoryText(text: string): MemoryToken[] {
- const counts = new Map()
- const matches = text.toLowerCase().match(TOKEN_PATTERN)
- if (!matches) return []
- for (const token of matches) {
- counts.set(token, (counts.get(token) ?? 0) + 1)
- }
- return [...counts].map(([token, frequency]) => ({ token, frequency }))
-}
-
-/** Tokens that index one memory item (search text of its payload). */
-export function buildMemoryItemTokens(
- kind: FluidMemoryKind,
- payload: FluidMemoryPayload,
-): MemoryToken[] {
- return tokenizeMemoryText(buildMemorySearchText(kind, payload))
-}
-
-function collectSearchParts(
- kind: FluidMemoryKind,
- payload: FluidMemoryPayload,
-): readonly string[] {
- switch (kind) {
- case "indicator_pref": {
- const p = payload as IndicatorPreferencePayload
- return [p.name, ...p.aliases, p.definition]
- }
- case "stance": {
- const p = payload as StancePayload
- return [p.statement, p.scope]
- }
- case "decision_rule": {
- const p = payload as DecisionRulePayload
- return [p.when, p.then]
- }
- case "entity_of_interest": {
- const p = payload as EntityOfInterestPayload
- return [p.name, ...p.aliases, ...(p.ticker ? [p.ticker] : [])]
- }
- }
-}
diff --git a/src/domains/memory/service.ts b/src/domains/memory/service.ts
deleted file mode 100644
index da772f65..00000000
--- a/src/domains/memory/service.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import "server-only"
-
-import { databaseRuntime } from "@/domains/workspace/database-runtime"
-import { memoryRepository } from "./repository"
-import type { ResolvedMemoryOperation } from "./resolve-operations"
-import type { FluidMemoryKind, MemoryDiffOperation } from "./types"
-import type { FluidMemoryItem } from "@/infrastructure/db/schema"
-
-type MemoryService = {
- readonly findDedupCandidates: (
- workspaceId: string,
- kind: FluidMemoryKind,
- tokens: readonly string[],
- limit: number,
- ) => Promise
- readonly applyOperations: (
- workspaceId: string,
- sourceMessageId: string | null,
- operations: readonly ResolvedMemoryOperation[],
- ) => Promise
-}
-
-const findDedupCandidates: MemoryService["findDedupCandidates"] = (
- workspaceId,
- kind,
- tokens,
- limit,
-) =>
- databaseRuntime.runPromise(
- memoryRepository.findDedupCandidatesEffect(
- workspaceId,
- kind,
- tokens,
- limit,
- ),
- )
-
-const applyOperations: MemoryService["applyOperations"] = (
- workspaceId,
- sourceMessageId,
- operations,
-) =>
- databaseRuntime.runPromise(
- memoryRepository.applyOperationsEffect(
- workspaceId,
- sourceMessageId,
- operations,
- ),
- )
-
-export const memoryService: MemoryService = {
- findDedupCandidates,
- applyOperations,
-}
diff --git a/src/domains/memory/types.ts b/src/domains/memory/types.ts
deleted file mode 100644
index dac7602d..00000000
--- a/src/domains/memory/types.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import { z } from "zod"
-
-/**
- * Fluid memory type contract.
- *
- * Four typed payload kinds, extracted from conversation turns. The DB
- * stores `payload` as jsonb; these schemas are the validation boundary on
- * both write (LLM output) and read (repository decode) paths.
- */
-
-export const fluidMemoryKinds = [
- "indicator_pref",
- "stance",
- "decision_rule",
- "entity_of_interest",
-] as const
-
-export type FluidMemoryKind = (typeof fluidMemoryKinds)[number]
-
-export const indicatorPreferencePayloadSchema = z.object({
- name: z.string().min(1),
- aliases: z.array(z.string()).default([]),
- definition: z.string().min(1),
- polarity: z.enum(["higher_better", "lower_better", "context"]),
- importance: z.enum(["core", "secondary"]),
- formulaHint: z.preprocess(
- (value) => (value === null ? undefined : value),
- z.string().optional(),
- ),
-})
-
-export const stancePayloadSchema = z.object({
- statement: z.string().min(1),
- scope: z.string().min(1),
- rationale: z.string().min(1),
-})
-
-export const decisionRulePayloadSchema = z.object({
- when: z.string().min(1),
- then: z.string().min(1),
- priority: z.enum(["high", "medium", "low"]),
- rationale: z.string().min(1),
-})
-
-export const entityOfInterestPayloadSchema = z.object({
- name: z.string().min(1),
- ticker: z.preprocess(
- (value) => (value === null ? undefined : value),
- z.string().optional(),
- ),
- aliases: z.array(z.string()).default([]),
- knowhereDocumentIds: z.array(z.string()).default([]),
- reason: z.string().min(1),
-})
-
-export type IndicatorPreferencePayload = z.infer<
- typeof indicatorPreferencePayloadSchema
->
-export type StancePayload = z.infer
-export type DecisionRulePayload = z.infer
-export type EntityOfInterestPayload = z.infer<
- typeof entityOfInterestPayloadSchema
->
-
-export type FluidMemoryPayload =
- | IndicatorPreferencePayload
- | StancePayload
- | DecisionRulePayload
- | EntityOfInterestPayload
-
-const payloadSchemas: Record> = {
- indicator_pref: indicatorPreferencePayloadSchema,
- stance: stancePayloadSchema,
- decision_rule: decisionRulePayloadSchema,
- entity_of_interest: entityOfInterestPayloadSchema,
-}
-
-export function isFluidMemoryKind(value: unknown): value is FluidMemoryKind {
- return (
- typeof value === "string" &&
- (fluidMemoryKinds as readonly string[]).includes(value)
- )
-}
-
-/** Decode a persisted jsonb payload; returns null when the row is malformed. */
-export function parseFluidMemoryPayload(
- kind: FluidMemoryKind,
- value: unknown,
-): FluidMemoryPayload | null {
- const result = payloadSchemas[kind].safeParse(value)
- return result.success ? result.data : null
-}
-
-/** One decided operation over the memory set; persisted into memory_diffs. */
-export type MemoryDiffOperation = {
- readonly op: "create" | "skip" | "merge" | "deprecate"
- readonly kind: FluidMemoryKind
- readonly itemId?: string
- readonly summary: string
- readonly reason?: string
-}
diff --git a/src/infrastructure/db/schema.ts b/src/infrastructure/db/schema.ts
index a3c98054..4e9470f3 100644
--- a/src/infrastructure/db/schema.ts
+++ b/src/infrastructure/db/schema.ts
@@ -1,9 +1,7 @@
import { sql } from "drizzle-orm";
import {
bigint,
- doublePrecision,
index,
- integer,
jsonb,
pgTable,
text,
@@ -340,139 +338,3 @@ export const chatMessages = pgTable(
export type ChatMessage = typeof chatMessages.$inferSelect;
export type NewChatMessage = typeof chatMessages.$inferInsert;
-
-/**
- * Fluid memory: typed insights extracted from human-AI conversation turns
- * (as opposed to "crystal memory", which is the parsed document knowledge
- * that stays upstream in Knowhere).
- *
- * One row per extracted insight. `kind` discriminates the typed `payload`
- * (see src/domains/memory/types.ts for the payload contract per kind):
- * - indicator_pref — a metric the user cares about (name, aliases,
- * polarity, importance)
- * - stance — a stated position that shapes judgement
- * - decision_rule — a when/then rule over indicators
- * - entity_of_interest — a company/topic the user tracks
- *
- * `abstract_l0` / `overview_l1` are the tiered sidecar summaries (L0 =
- * one line for pre-filter/dedup context, L1 = short paragraph for later
- * cognition injection). L2 is the payload itself.
- *
- * Lifecycle: rows start `active`; user revisions deprecate rather than
- * delete (conservative merge policy), with `version` bumped on merge.
- *
- * `source_message_id` points at the assistant message of the turn the
- * insight was extracted from; it is set-null on message deletion because
- * the insight outlives any single turn.
- */
-export const fluidMemoryItems = pgTable(
- "fluid_memory_items",
- {
- id: uuid("id").primaryKey().defaultRandom(),
- workspaceId: uuid("workspace_id")
- .notNull()
- .references(() => workspaces.id, { onDelete: "cascade" }),
- kind: text("kind").notNull(),
- payload: jsonb("payload").notNull(),
- abstractL0: text("abstract_l0").notNull(),
- overviewL1: text("overview_l1").notNull(),
- sourceMessageId: uuid("source_message_id").references(
- () => chatMessages.id,
- { onDelete: "set null" },
- ),
- confidence: doublePrecision("confidence").notNull(),
- status: text("status").notNull(),
- version: integer("version").notNull().default(1),
- createdAt: timestamp("created_at", { withTimezone: true })
- .notNull()
- .defaultNow(),
- updatedAt: timestamp("updated_at", { withTimezone: true })
- .notNull()
- .defaultNow(),
- },
- (t) => [
- // Workspace lifecycle scans (active vs deprecated).
- index("fluid_memory_items_workspace_status_idx").on(
- t.workspaceId,
- t.status,
- ),
- index("fluid_memory_items_workspace_kind_idx").on(t.workspaceId, t.kind),
- ],
-);
-
-export type FluidMemoryItem = typeof fluidMemoryItems.$inferSelect;
-export type NewFluidMemoryItem = typeof fluidMemoryItems.$inferInsert;
-
-/**
- * Lexical inverted index over active fluid memory items, used to retrieve
- * dedup candidates at extraction time instead of loading the whole memory
- * set into the prompt. One row per (item, token); `frequency` counts token
- * occurrences in the item's search text.
- *
- * Invariant: token rows exist iff the owning item is `active`. Writers keep
- * this in sync — create inserts rows, merge replaces them, deprecate deletes
- * them — so lookups scan tokens alone (no status join) and never surface a
- * deprecated item.
- *
- * Tokenization mirrors Knowhere map-nav: single CJK characters plus
- * `[a-z0-9_]+` runs. Scoring is idf-weighted token overlap computed in SQL,
- * keeping the mechanism on portable Postgres (no pg_trgm/pgvector).
- */
-export const fluidMemoryTokens = pgTable(
- "fluid_memory_tokens",
- {
- id: uuid("id").primaryKey().defaultRandom(),
- workspaceId: uuid("workspace_id")
- .notNull()
- .references(() => workspaces.id, { onDelete: "cascade" }),
- itemId: uuid("item_id")
- .notNull()
- .references(() => fluidMemoryItems.id, { onDelete: "cascade" }),
- kind: text("kind").notNull(),
- token: text("token").notNull(),
- frequency: integer("frequency").notNull().default(1),
- },
- (t) => [
- // Lookup: candidate tokens within a workspace + kind scope.
- index("fluid_memory_tokens_lookup_idx").on(
- t.workspaceId,
- t.kind,
- t.token,
- ),
- // Rebuild/delete a single item's rows on merge/deprecate.
- index("fluid_memory_tokens_item_idx").on(t.itemId),
- ],
-);
-
-export type FluidMemoryToken = typeof fluidMemoryTokens.$inferSelect;
-export type NewFluidMemoryToken = typeof fluidMemoryTokens.$inferInsert;
-
-/**
- * Append-only audit of extraction decisions, one row per processed turn.
- * `operations` is a JSONB array of { op, kind, itemId?, summary, reason? }
- * records (op = create | skip | merge | deprecate), mirroring OpenViking's
- * memory_diff.json so memory growth stays observable and reversible.
- */
-export const memoryDiffs = pgTable(
- "memory_diffs",
- {
- id: uuid("id").primaryKey().defaultRandom(),
- workspaceId: uuid("workspace_id")
- .notNull()
- .references(() => workspaces.id, { onDelete: "cascade" }),
- sourceMessageId: uuid("source_message_id").references(
- () => chatMessages.id,
- { onDelete: "set null" },
- ),
- operations: jsonb("operations").notNull(),
- createdAt: timestamp("created_at", { withTimezone: true })
- .notNull()
- .defaultNow(),
- },
- (t) => [
- index("memory_diffs_workspace_created_idx").on(t.workspaceId, t.createdAt),
- ],
-);
-
-export type MemoryDiff = typeof memoryDiffs.$inferSelect;
-export type NewMemoryDiff = typeof memoryDiffs.$inferInsert;
diff --git a/src/integrations/knowhere.test.ts b/src/integrations/knowhere.test.ts
index 74b8c253..a2c4845d 100644
--- a/src/integrations/knowhere.test.ts
+++ b/src/integrations/knowhere.test.ts
@@ -58,6 +58,7 @@ describe("makeKnowhereClient", () => {
expect(constructorSpy).toHaveBeenCalledWith({
apiKey: "sk_test",
baseURL: "https://api-staging.knowhereto.ai",
+ timeout: 120_000,
});
});
diff --git a/src/integrations/knowhere.ts b/src/integrations/knowhere.ts
index e55fc0bd..a1ca2ed5 100644
--- a/src/integrations/knowhere.ts
+++ b/src/integrations/knowhere.ts
@@ -37,6 +37,7 @@ export function makeKnowhereClient(apiKey: string): Knowhere {
const options: ConstructorParameters[0] = {
apiKey,
baseURL: process.env.KNOWHERE_BASE_URL,
+ timeout: 120_000,
}
const client = new Knowhere(options)
return wrapKnowhereClient(client)
diff --git a/src/integrations/memento/client.ts b/src/integrations/memento/client.ts
new file mode 100644
index 00000000..0b0be90a
--- /dev/null
+++ b/src/integrations/memento/client.ts
@@ -0,0 +1,75 @@
+import "server-only"
+
+import {
+ FetchHttpClient,
+ HttpClient,
+ HttpClientRequest,
+} from "@effect/platform"
+import { Effect } from "effect"
+
+import { summarizeUnknownError } from "@/lib/format-log-value"
+import { logger } from "@/lib/logger"
+import { readMementoConfig } from "./config"
+
+export type MementoCaptureInput = {
+ readonly workspaceId: string
+ readonly sourceMessageId: string | null
+ readonly userText: string
+ readonly assistantText: string
+ readonly referencedDocumentIds: readonly string[]
+}
+
+export type MementoActivationInput = {
+ readonly workspaceId: string
+ readonly unitType: "fluid_memory" | "crystal_chunk"
+ readonly unitRef: string
+}
+
+export async function captureMemoryTurn(
+ input: MementoCaptureInput,
+): Promise {
+ try {
+ await postJson("/turns/capture", input)
+ } catch (error) {
+ logger.warn("chat: failed to capture memory turn", {
+ workspaceId: input.workspaceId,
+ error: summarizeUnknownError(error),
+ })
+ }
+}
+
+export async function recordActivations(
+ activations: readonly MementoActivationInput[],
+): Promise {
+ if (activations.length === 0) return
+
+ try {
+ await postJson("/activations", { activations })
+ } catch (error) {
+ logger.warn("chat: failed to record activations", {
+ workspaceId: activations[0]?.workspaceId,
+ count: activations.length,
+ error: summarizeUnknownError(error),
+ })
+ }
+}
+
+async function postJson(path: string, body: unknown): Promise {
+ const status = await Effect.runPromise(
+ Effect.gen(function* () {
+ const { baseUrl, serviceKey } = readMementoConfig()
+ const request = yield* HttpClientRequest.post(`${baseUrl}${path}`).pipe(
+ HttpClientRequest.setHeader(
+ "Authorization",
+ `Bearer ${serviceKey}`,
+ ),
+ HttpClientRequest.bodyJson(body),
+ )
+ const response = yield* HttpClient.execute(request)
+ return response.status
+ }).pipe(Effect.provide(FetchHttpClient.layer)),
+ )
+ if (status < 200 || status >= 300) {
+ throw new Error(`memento ${path}: HTTP ${status}`)
+ }
+}
diff --git a/src/integrations/memento/config.ts b/src/integrations/memento/config.ts
new file mode 100644
index 00000000..29eb2343
--- /dev/null
+++ b/src/integrations/memento/config.ts
@@ -0,0 +1,18 @@
+import "server-only"
+
+export type MementoConfig = {
+ readonly baseUrl: string
+ readonly serviceKey: string
+}
+
+export function readMementoConfig(): MementoConfig {
+ const baseUrl = process.env.MEMENTO_BASE_URL
+ const serviceKey = process.env.MEMENTO_SERVICE_KEY
+ if (!baseUrl) {
+ throw new Error("MEMENTO_BASE_URL is required.")
+ }
+ if (!serviceKey) {
+ throw new Error("MEMENTO_SERVICE_KEY is required.")
+ }
+ return { baseUrl, serviceKey }
+}
diff --git a/src/integrations/memento/memory-tools.ts b/src/integrations/memento/memory-tools.ts
new file mode 100644
index 00000000..052f160f
--- /dev/null
+++ b/src/integrations/memento/memory-tools.ts
@@ -0,0 +1,105 @@
+import "server-only"
+
+import { Client } from "@modelcontextprotocol/sdk/client/index.js"
+import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
+
+import type {
+ MemorySearchItem,
+ MemorySearchKind,
+ MemorySearchRequest,
+ MemorySearchResponse,
+ MemoryToolRuntime,
+} from "@/agent-harness"
+import { memorySearchKinds } from "@/agent-harness"
+import { readMementoConfig } from "./config"
+
+type MementoMemoryToolsInput = {
+ readonly workspaceId: string
+}
+
+export const mementoMemoryTools = {
+ createRuntime(input: MementoMemoryToolsInput): MemoryToolRuntime {
+ return {
+ search: (request) => searchWorkspaceMemory(input.workspaceId, request),
+ }
+ },
+} as const
+
+async function searchWorkspaceMemory(
+ workspaceId: string,
+ request: MemorySearchRequest,
+): Promise {
+ const { baseUrl, serviceKey } = readMementoConfig()
+ const transport = new StreamableHTTPClientTransport(new URL(`${baseUrl}/mcp`), {
+ requestInit: {
+ headers: {
+ Authorization: `Bearer ${serviceKey}`,
+ },
+ },
+ })
+ const client = new Client({
+ name: "knowhere-notebook",
+ version: "0.1.0",
+ })
+ await client.connect(transport)
+ try {
+ const result = await client.callTool({
+ name: "memory_search",
+ arguments: {
+ workspaceId,
+ query: request.query,
+ ...(request.kinds ? { kinds: request.kinds } : {}),
+ },
+ })
+ if (result.isError) {
+ throw new Error("memento memory_search failed")
+ }
+ return parseMemorySearchResponse(result.structuredContent)
+ } finally {
+ await client.close()
+ }
+}
+
+function parseMemorySearchResponse(value: unknown): MemorySearchResponse {
+ if (!value || typeof value !== "object") {
+ throw new Error("memento memory_search returned no structured content")
+ }
+ const record = value as Record
+ if (typeof record.query !== "string" || !Array.isArray(record.items)) {
+ throw new Error("memento memory_search structured content is invalid")
+ }
+ return {
+ query: record.query,
+ items: record.items.map(parseMemorySearchItem),
+ }
+}
+
+function parseMemorySearchItem(value: unknown): MemorySearchItem {
+ if (!value || typeof value !== "object") {
+ throw new Error("memento memory_search item is invalid")
+ }
+ const item = value as Record
+ if (
+ typeof item.ref !== "string" ||
+ typeof item.itemId !== "string" ||
+ typeof item.abstractL0 !== "string" ||
+ typeof item.overviewL1 !== "string" ||
+ !isMemorySearchKind(item.kind)
+ ) {
+ throw new Error("memento memory_search item is invalid")
+ }
+ return {
+ ref: item.ref,
+ itemId: item.itemId,
+ kind: item.kind,
+ abstractL0: item.abstractL0,
+ overviewL1: item.overviewL1,
+ }
+}
+
+function isMemorySearchKind(value: unknown): value is MemorySearchKind {
+ return (
+ typeof value === "string" &&
+ (memorySearchKinds as readonly string[]).includes(value)
+ )
+}