diff --git a/.env.example b/.env.example index 69aad21c34..0035085ac1 100644 --- a/.env.example +++ b/.env.example @@ -1345,6 +1345,15 @@ APP_LOG_TO_FILE=true # ELECTRON_SMOKE_KEEP_DATA=0 # ELECTRON_SMOKE_STREAM_LOGS=0 +# Memory engine (plan 21) +# MEMORY_EMBEDDING_CACHE_TTL_MS=300000 # default 5 min +# MEMORY_EMBEDDING_CACHE_MAX=1000 # default 1000 entries +# MEMORY_TRANSFORMERS_MODEL=Xenova/all-MiniLM-L6-v2 +# MEMORY_STATIC_MODEL=minishlab/potion-base-8M # HF repo id (download once) +# MEMORY_STATIC_CACHE_DIR= # default /embeddings +# MEMORY_VEC_TOP_K=20 # default top-K for vector search +# MEMORY_RRF_K=60 # RRF k constant (sqlite-vec hybrid recipe) +# HF_HUB_ENDPOINT=https://huggingface.co # override Hugging Face Hub base URL for static potion downloads # AgentBridge + Traffic Inspector (Group A) # AgentBridge diff --git a/.source/browser.ts b/.source/browser.ts index 0a7d5144e9..fd85e0430b 100644 --- a/.source/browser.ts +++ b/.source/browser.ts @@ -7,6 +7,6 @@ const create = browser(); const browserCollections = { - docs: create.doc("docs", {"architecture/ARCHITECTURE.md": () => import("../docs/architecture/ARCHITECTURE.md?collection=docs"), "architecture/AUTHZ_GUIDE.md": () => import("../docs/architecture/AUTHZ_GUIDE.md?collection=docs"), "architecture/CODEBASE_DOCUMENTATION.md": () => import("../docs/architecture/CODEBASE_DOCUMENTATION.md?collection=docs"), "architecture/MONITORING_SECTIONS.md": () => import("../docs/architecture/MONITORING_SECTIONS.md?collection=docs"), "architecture/REPOSITORY_MAP.md": () => import("../docs/architecture/REPOSITORY_MAP.md?collection=docs"), "architecture/RESILIENCE_GUIDE.md": () => import("../docs/architecture/RESILIENCE_GUIDE.md?collection=docs"), "compression/COMPRESSION_ENGINES.md": () => import("../docs/compression/COMPRESSION_ENGINES.md?collection=docs"), "compression/COMPRESSION_GUIDE.md": () => import("../docs/compression/COMPRESSION_GUIDE.md?collection=docs"), "compression/COMPRESSION_LANGUAGE_PACKS.md": () => import("../docs/compression/COMPRESSION_LANGUAGE_PACKS.md?collection=docs"), "compression/COMPRESSION_RULES_FORMAT.md": () => import("../docs/compression/COMPRESSION_RULES_FORMAT.md?collection=docs"), "compression/RTK_COMPRESSION.md": () => import("../docs/compression/RTK_COMPRESSION.md?collection=docs"), "frameworks/A2A-SERVER.md": () => import("../docs/frameworks/A2A-SERVER.md?collection=docs"), "frameworks/AGENT-SKILLS.md": () => import("../docs/frameworks/AGENT-SKILLS.md?collection=docs"), "frameworks/AGENTBRIDGE.md": () => import("../docs/frameworks/AGENTBRIDGE.md?collection=docs"), "frameworks/AGENT_PROTOCOLS_GUIDE.md": () => import("../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs"), "frameworks/CLOUD_AGENT.md": () => import("../docs/frameworks/CLOUD_AGENT.md?collection=docs"), "frameworks/EMBEDDED-SERVICES.md": () => import("../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs"), "frameworks/EVALS.md": () => import("../docs/frameworks/EVALS.md?collection=docs"), "frameworks/GAMIFICATION.md": () => import("../docs/frameworks/GAMIFICATION.md?collection=docs"), "frameworks/MCP-SERVER.md": () => import("../docs/frameworks/MCP-SERVER.md?collection=docs"), "frameworks/MEMORY.md": () => import("../docs/frameworks/MEMORY.md?collection=docs"), "frameworks/OPENCODE.md": () => import("../docs/frameworks/OPENCODE.md?collection=docs"), "frameworks/SKILLS.md": () => import("../docs/frameworks/SKILLS.md?collection=docs"), "frameworks/TRAFFIC_INSPECTOR.md": () => import("../docs/frameworks/TRAFFIC_INSPECTOR.md?collection=docs"), "frameworks/WEBHOOKS.md": () => import("../docs/frameworks/WEBHOOKS.md?collection=docs"), "guides/DOCKER_GUIDE.md": () => import("../docs/guides/DOCKER_GUIDE.md?collection=docs"), "guides/ELECTRON_GUIDE.md": () => import("../docs/guides/ELECTRON_GUIDE.md?collection=docs"), "guides/FEATURES.md": () => import("../docs/guides/FEATURES.md?collection=docs"), "guides/I18N.md": () => import("../docs/guides/I18N.md?collection=docs"), "guides/KIRO_SETUP.md": () => import("../docs/guides/KIRO_SETUP.md?collection=docs"), "guides/PWA_GUIDE.md": () => import("../docs/guides/PWA_GUIDE.md?collection=docs"), "guides/SETUP_GUIDE.md": () => import("../docs/guides/SETUP_GUIDE.md?collection=docs"), "guides/TERMUX_GUIDE.md": () => import("../docs/guides/TERMUX_GUIDE.md?collection=docs"), "guides/TROUBLESHOOTING.md": () => import("../docs/guides/TROUBLESHOOTING.md?collection=docs"), "guides/UNINSTALL.md": () => import("../docs/guides/UNINSTALL.md?collection=docs"), "guides/USER_GUIDE.md": () => import("../docs/guides/USER_GUIDE.md?collection=docs"), "ops/COVERAGE_PLAN.md": () => import("../docs/ops/COVERAGE_PLAN.md?collection=docs"), "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": () => import("../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs"), "ops/FLY_IO_DEPLOYMENT_GUIDE.md": () => import("../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs"), "ops/PROXY_GUIDE.md": () => import("../docs/ops/PROXY_GUIDE.md?collection=docs"), "ops/RELEASE_CHECKLIST.md": () => import("../docs/ops/RELEASE_CHECKLIST.md?collection=docs"), "ops/SQLITE_RUNTIME.md": () => import("../docs/ops/SQLITE_RUNTIME.md?collection=docs"), "ops/TUNNELS_GUIDE.md": () => import("../docs/ops/TUNNELS_GUIDE.md?collection=docs"), "ops/VM_DEPLOYMENT_GUIDE.md": () => import("../docs/ops/VM_DEPLOYMENT_GUIDE.md?collection=docs"), "reference/API_REFERENCE.md": () => import("../docs/reference/API_REFERENCE.md?collection=docs"), "reference/CLI-TOOLS.md": () => import("../docs/reference/CLI-TOOLS.md?collection=docs"), "reference/ENVIRONMENT.md": () => import("../docs/reference/ENVIRONMENT.md?collection=docs"), "reference/FREE_TIERS.md": () => import("../docs/reference/FREE_TIERS.md?collection=docs"), "reference/PROVIDER_REFERENCE.md": () => import("../docs/reference/PROVIDER_REFERENCE.md?collection=docs"), "routing/AUTO-COMBO.md": () => import("../docs/routing/AUTO-COMBO.md?collection=docs"), "routing/QUOTA_SHARE.md": () => import("../docs/routing/QUOTA_SHARE.md?collection=docs"), "routing/REASONING_REPLAY.md": () => import("../docs/routing/REASONING_REPLAY.md?collection=docs"), "security/CLI_TOKEN.md": () => import("../docs/security/CLI_TOKEN.md?collection=docs"), "security/CLI_TOKEN_AUTH.md": () => import("../docs/security/CLI_TOKEN_AUTH.md?collection=docs"), "security/COMPLIANCE.md": () => import("../docs/security/COMPLIANCE.md?collection=docs"), "security/ERROR_SANITIZATION.md": () => import("../docs/security/ERROR_SANITIZATION.md?collection=docs"), "security/GUARDRAILS.md": () => import("../docs/security/GUARDRAILS.md?collection=docs"), "security/PUBLIC_CREDS.md": () => import("../docs/security/PUBLIC_CREDS.md?collection=docs"), "security/ROUTE_GUARD_TIERS.md": () => import("../docs/security/ROUTE_GUARD_TIERS.md?collection=docs"), "security/SOCKET_DEV_FINDINGS.md": () => import("../docs/security/SOCKET_DEV_FINDINGS.md?collection=docs"), "security/STEALTH_GUIDE.md": () => import("../docs/security/STEALTH_GUIDE.md?collection=docs"), }), + docs: create.doc("docs", {"architecture/ARCHITECTURE.md": () => import("../docs/architecture/ARCHITECTURE.md?collection=docs"), "architecture/AUTHZ_GUIDE.md": () => import("../docs/architecture/AUTHZ_GUIDE.md?collection=docs"), "architecture/CODEBASE_DOCUMENTATION.md": () => import("../docs/architecture/CODEBASE_DOCUMENTATION.md?collection=docs"), "architecture/MONITORING_SECTIONS.md": () => import("../docs/architecture/MONITORING_SECTIONS.md?collection=docs"), "architecture/REPOSITORY_MAP.md": () => import("../docs/architecture/REPOSITORY_MAP.md?collection=docs"), "architecture/RESILIENCE_GUIDE.md": () => import("../docs/architecture/RESILIENCE_GUIDE.md?collection=docs"), "compression/COMPRESSION_ENGINES.md": () => import("../docs/compression/COMPRESSION_ENGINES.md?collection=docs"), "compression/COMPRESSION_GUIDE.md": () => import("../docs/compression/COMPRESSION_GUIDE.md?collection=docs"), "compression/COMPRESSION_LANGUAGE_PACKS.md": () => import("../docs/compression/COMPRESSION_LANGUAGE_PACKS.md?collection=docs"), "compression/COMPRESSION_RULES_FORMAT.md": () => import("../docs/compression/COMPRESSION_RULES_FORMAT.md?collection=docs"), "compression/RTK_COMPRESSION.md": () => import("../docs/compression/RTK_COMPRESSION.md?collection=docs"), "guides/DOCKER_GUIDE.md": () => import("../docs/guides/DOCKER_GUIDE.md?collection=docs"), "guides/ELECTRON_GUIDE.md": () => import("../docs/guides/ELECTRON_GUIDE.md?collection=docs"), "guides/FEATURES.md": () => import("../docs/guides/FEATURES.md?collection=docs"), "guides/I18N.md": () => import("../docs/guides/I18N.md?collection=docs"), "guides/KIRO_SETUP.md": () => import("../docs/guides/KIRO_SETUP.md?collection=docs"), "guides/PWA_GUIDE.md": () => import("../docs/guides/PWA_GUIDE.md?collection=docs"), "guides/SETUP_GUIDE.md": () => import("../docs/guides/SETUP_GUIDE.md?collection=docs"), "guides/TERMUX_GUIDE.md": () => import("../docs/guides/TERMUX_GUIDE.md?collection=docs"), "guides/TROUBLESHOOTING.md": () => import("../docs/guides/TROUBLESHOOTING.md?collection=docs"), "guides/UNINSTALL.md": () => import("../docs/guides/UNINSTALL.md?collection=docs"), "guides/USER_GUIDE.md": () => import("../docs/guides/USER_GUIDE.md?collection=docs"), "frameworks/A2A-SERVER.md": () => import("../docs/frameworks/A2A-SERVER.md?collection=docs"), "frameworks/AGENT-SKILLS.md": () => import("../docs/frameworks/AGENT-SKILLS.md?collection=docs"), "frameworks/AGENTBRIDGE.md": () => import("../docs/frameworks/AGENTBRIDGE.md?collection=docs"), "frameworks/AGENT_PROTOCOLS_GUIDE.md": () => import("../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs"), "frameworks/CLOUD_AGENT.md": () => import("../docs/frameworks/CLOUD_AGENT.md?collection=docs"), "frameworks/EMBEDDED-SERVICES.md": () => import("../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs"), "frameworks/EVALS.md": () => import("../docs/frameworks/EVALS.md?collection=docs"), "frameworks/GAMIFICATION.md": () => import("../docs/frameworks/GAMIFICATION.md?collection=docs"), "frameworks/MCP-SERVER.md": () => import("../docs/frameworks/MCP-SERVER.md?collection=docs"), "frameworks/MEMORY.md": () => import("../docs/frameworks/MEMORY.md?collection=docs"), "frameworks/OPENCODE.md": () => import("../docs/frameworks/OPENCODE.md?collection=docs"), "frameworks/SKILLS.md": () => import("../docs/frameworks/SKILLS.md?collection=docs"), "frameworks/TRAFFIC_INSPECTOR.md": () => import("../docs/frameworks/TRAFFIC_INSPECTOR.md?collection=docs"), "frameworks/WEBHOOKS.md": () => import("../docs/frameworks/WEBHOOKS.md?collection=docs"), "ops/COVERAGE_PLAN.md": () => import("../docs/ops/COVERAGE_PLAN.md?collection=docs"), "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": () => import("../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs"), "ops/FLY_IO_DEPLOYMENT_GUIDE.md": () => import("../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs"), "ops/PROXY_GUIDE.md": () => import("../docs/ops/PROXY_GUIDE.md?collection=docs"), "ops/RELEASE_CHECKLIST.md": () => import("../docs/ops/RELEASE_CHECKLIST.md?collection=docs"), "ops/SQLITE_RUNTIME.md": () => import("../docs/ops/SQLITE_RUNTIME.md?collection=docs"), "ops/TUNNELS_GUIDE.md": () => import("../docs/ops/TUNNELS_GUIDE.md?collection=docs"), "ops/VM_DEPLOYMENT_GUIDE.md": () => import("../docs/ops/VM_DEPLOYMENT_GUIDE.md?collection=docs"), "reference/API_REFERENCE.md": () => import("../docs/reference/API_REFERENCE.md?collection=docs"), "reference/CLI-TOOLS.md": () => import("../docs/reference/CLI-TOOLS.md?collection=docs"), "reference/ENVIRONMENT.md": () => import("../docs/reference/ENVIRONMENT.md?collection=docs"), "reference/FREE_TIERS.md": () => import("../docs/reference/FREE_TIERS.md?collection=docs"), "reference/PROVIDER_REFERENCE.md": () => import("../docs/reference/PROVIDER_REFERENCE.md?collection=docs"), "routing/AUTO-COMBO.md": () => import("../docs/routing/AUTO-COMBO.md?collection=docs"), "routing/QUOTA_SHARE.md": () => import("../docs/routing/QUOTA_SHARE.md?collection=docs"), "routing/REASONING_REPLAY.md": () => import("../docs/routing/REASONING_REPLAY.md?collection=docs"), "security/CLI_TOKEN.md": () => import("../docs/security/CLI_TOKEN.md?collection=docs"), "security/CLI_TOKEN_AUTH.md": () => import("../docs/security/CLI_TOKEN_AUTH.md?collection=docs"), "security/COMPLIANCE.md": () => import("../docs/security/COMPLIANCE.md?collection=docs"), "security/ERROR_SANITIZATION.md": () => import("../docs/security/ERROR_SANITIZATION.md?collection=docs"), "security/GUARDRAILS.md": () => import("../docs/security/GUARDRAILS.md?collection=docs"), "security/PUBLIC_CREDS.md": () => import("../docs/security/PUBLIC_CREDS.md?collection=docs"), "security/ROUTE_GUARD_TIERS.md": () => import("../docs/security/ROUTE_GUARD_TIERS.md?collection=docs"), "security/SOCKET_DEV_FINDINGS.md": () => import("../docs/security/SOCKET_DEV_FINDINGS.md?collection=docs"), "security/STEALTH_GUIDE.md": () => import("../docs/security/STEALTH_GUIDE.md?collection=docs"), }), }; export default browserCollections; \ No newline at end of file diff --git a/.source/server.ts b/.source/server.ts index a04e3160c9..b61a438041 100644 --- a/.source/server.ts +++ b/.source/server.ts @@ -34,31 +34,31 @@ import * as __fd_glob_39 from "../docs/ops/PROXY_GUIDE.md?collection=docs" import * as __fd_glob_38 from "../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs" import * as __fd_glob_37 from "../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs" import * as __fd_glob_36 from "../docs/ops/COVERAGE_PLAN.md?collection=docs" -import * as __fd_glob_35 from "../docs/guides/USER_GUIDE.md?collection=docs" -import * as __fd_glob_34 from "../docs/guides/UNINSTALL.md?collection=docs" -import * as __fd_glob_33 from "../docs/guides/TROUBLESHOOTING.md?collection=docs" -import * as __fd_glob_32 from "../docs/guides/TERMUX_GUIDE.md?collection=docs" -import * as __fd_glob_31 from "../docs/guides/SETUP_GUIDE.md?collection=docs" -import * as __fd_glob_30 from "../docs/guides/PWA_GUIDE.md?collection=docs" -import * as __fd_glob_29 from "../docs/guides/KIRO_SETUP.md?collection=docs" -import * as __fd_glob_28 from "../docs/guides/I18N.md?collection=docs" -import * as __fd_glob_27 from "../docs/guides/FEATURES.md?collection=docs" -import * as __fd_glob_26 from "../docs/guides/ELECTRON_GUIDE.md?collection=docs" -import * as __fd_glob_25 from "../docs/guides/DOCKER_GUIDE.md?collection=docs" -import * as __fd_glob_24 from "../docs/frameworks/WEBHOOKS.md?collection=docs" -import * as __fd_glob_23 from "../docs/frameworks/TRAFFIC_INSPECTOR.md?collection=docs" -import * as __fd_glob_22 from "../docs/frameworks/SKILLS.md?collection=docs" -import * as __fd_glob_21 from "../docs/frameworks/OPENCODE.md?collection=docs" -import * as __fd_glob_20 from "../docs/frameworks/MEMORY.md?collection=docs" -import * as __fd_glob_19 from "../docs/frameworks/MCP-SERVER.md?collection=docs" -import * as __fd_glob_18 from "../docs/frameworks/GAMIFICATION.md?collection=docs" -import * as __fd_glob_17 from "../docs/frameworks/EVALS.md?collection=docs" -import * as __fd_glob_16 from "../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs" -import * as __fd_glob_15 from "../docs/frameworks/CLOUD_AGENT.md?collection=docs" -import * as __fd_glob_14 from "../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs" -import * as __fd_glob_13 from "../docs/frameworks/AGENTBRIDGE.md?collection=docs" -import * as __fd_glob_12 from "../docs/frameworks/AGENT-SKILLS.md?collection=docs" -import * as __fd_glob_11 from "../docs/frameworks/A2A-SERVER.md?collection=docs" +import * as __fd_glob_35 from "../docs/frameworks/WEBHOOKS.md?collection=docs" +import * as __fd_glob_34 from "../docs/frameworks/TRAFFIC_INSPECTOR.md?collection=docs" +import * as __fd_glob_33 from "../docs/frameworks/SKILLS.md?collection=docs" +import * as __fd_glob_32 from "../docs/frameworks/OPENCODE.md?collection=docs" +import * as __fd_glob_31 from "../docs/frameworks/MEMORY.md?collection=docs" +import * as __fd_glob_30 from "../docs/frameworks/MCP-SERVER.md?collection=docs" +import * as __fd_glob_29 from "../docs/frameworks/GAMIFICATION.md?collection=docs" +import * as __fd_glob_28 from "../docs/frameworks/EVALS.md?collection=docs" +import * as __fd_glob_27 from "../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs" +import * as __fd_glob_26 from "../docs/frameworks/CLOUD_AGENT.md?collection=docs" +import * as __fd_glob_25 from "../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs" +import * as __fd_glob_24 from "../docs/frameworks/AGENTBRIDGE.md?collection=docs" +import * as __fd_glob_23 from "../docs/frameworks/AGENT-SKILLS.md?collection=docs" +import * as __fd_glob_22 from "../docs/frameworks/A2A-SERVER.md?collection=docs" +import * as __fd_glob_21 from "../docs/guides/USER_GUIDE.md?collection=docs" +import * as __fd_glob_20 from "../docs/guides/UNINSTALL.md?collection=docs" +import * as __fd_glob_19 from "../docs/guides/TROUBLESHOOTING.md?collection=docs" +import * as __fd_glob_18 from "../docs/guides/TERMUX_GUIDE.md?collection=docs" +import * as __fd_glob_17 from "../docs/guides/SETUP_GUIDE.md?collection=docs" +import * as __fd_glob_16 from "../docs/guides/PWA_GUIDE.md?collection=docs" +import * as __fd_glob_15 from "../docs/guides/KIRO_SETUP.md?collection=docs" +import * as __fd_glob_14 from "../docs/guides/I18N.md?collection=docs" +import * as __fd_glob_13 from "../docs/guides/FEATURES.md?collection=docs" +import * as __fd_glob_12 from "../docs/guides/ELECTRON_GUIDE.md?collection=docs" +import * as __fd_glob_11 from "../docs/guides/DOCKER_GUIDE.md?collection=docs" import * as __fd_glob_10 from "../docs/compression/RTK_COMPRESSION.md?collection=docs" import * as __fd_glob_9 from "../docs/compression/COMPRESSION_RULES_FORMAT.md?collection=docs" import * as __fd_glob_8 from "../docs/compression/COMPRESSION_LANGUAGE_PACKS.md?collection=docs" @@ -78,4 +78,4 @@ const create = server({"doc":{"passthroughs":["extractedReferences"]}}); -export const docs = await create.docs("docs", "docs", {"meta.json": __fd_glob_61, "architecture/meta.json": __fd_glob_62, "compression/meta.json": __fd_glob_63, "frameworks/meta.json": __fd_glob_64, "guides/meta.json": __fd_glob_65, "ops/meta.json": __fd_glob_66, "reference/meta.json": __fd_glob_67, "reference/openapi.yaml": __fd_glob_68, "routing/meta.json": __fd_glob_69, "security/meta.json": __fd_glob_70, }, {"architecture/ARCHITECTURE.md": __fd_glob_0, "architecture/AUTHZ_GUIDE.md": __fd_glob_1, "architecture/CODEBASE_DOCUMENTATION.md": __fd_glob_2, "architecture/MONITORING_SECTIONS.md": __fd_glob_3, "architecture/REPOSITORY_MAP.md": __fd_glob_4, "architecture/RESILIENCE_GUIDE.md": __fd_glob_5, "compression/COMPRESSION_ENGINES.md": __fd_glob_6, "compression/COMPRESSION_GUIDE.md": __fd_glob_7, "compression/COMPRESSION_LANGUAGE_PACKS.md": __fd_glob_8, "compression/COMPRESSION_RULES_FORMAT.md": __fd_glob_9, "compression/RTK_COMPRESSION.md": __fd_glob_10, "frameworks/A2A-SERVER.md": __fd_glob_11, "frameworks/AGENT-SKILLS.md": __fd_glob_12, "frameworks/AGENTBRIDGE.md": __fd_glob_13, "frameworks/AGENT_PROTOCOLS_GUIDE.md": __fd_glob_14, "frameworks/CLOUD_AGENT.md": __fd_glob_15, "frameworks/EMBEDDED-SERVICES.md": __fd_glob_16, "frameworks/EVALS.md": __fd_glob_17, "frameworks/GAMIFICATION.md": __fd_glob_18, "frameworks/MCP-SERVER.md": __fd_glob_19, "frameworks/MEMORY.md": __fd_glob_20, "frameworks/OPENCODE.md": __fd_glob_21, "frameworks/SKILLS.md": __fd_glob_22, "frameworks/TRAFFIC_INSPECTOR.md": __fd_glob_23, "frameworks/WEBHOOKS.md": __fd_glob_24, "guides/DOCKER_GUIDE.md": __fd_glob_25, "guides/ELECTRON_GUIDE.md": __fd_glob_26, "guides/FEATURES.md": __fd_glob_27, "guides/I18N.md": __fd_glob_28, "guides/KIRO_SETUP.md": __fd_glob_29, "guides/PWA_GUIDE.md": __fd_glob_30, "guides/SETUP_GUIDE.md": __fd_glob_31, "guides/TERMUX_GUIDE.md": __fd_glob_32, "guides/TROUBLESHOOTING.md": __fd_glob_33, "guides/UNINSTALL.md": __fd_glob_34, "guides/USER_GUIDE.md": __fd_glob_35, "ops/COVERAGE_PLAN.md": __fd_glob_36, "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": __fd_glob_37, "ops/FLY_IO_DEPLOYMENT_GUIDE.md": __fd_glob_38, "ops/PROXY_GUIDE.md": __fd_glob_39, "ops/RELEASE_CHECKLIST.md": __fd_glob_40, "ops/SQLITE_RUNTIME.md": __fd_glob_41, "ops/TUNNELS_GUIDE.md": __fd_glob_42, "ops/VM_DEPLOYMENT_GUIDE.md": __fd_glob_43, "reference/API_REFERENCE.md": __fd_glob_44, "reference/CLI-TOOLS.md": __fd_glob_45, "reference/ENVIRONMENT.md": __fd_glob_46, "reference/FREE_TIERS.md": __fd_glob_47, "reference/PROVIDER_REFERENCE.md": __fd_glob_48, "routing/AUTO-COMBO.md": __fd_glob_49, "routing/QUOTA_SHARE.md": __fd_glob_50, "routing/REASONING_REPLAY.md": __fd_glob_51, "security/CLI_TOKEN.md": __fd_glob_52, "security/CLI_TOKEN_AUTH.md": __fd_glob_53, "security/COMPLIANCE.md": __fd_glob_54, "security/ERROR_SANITIZATION.md": __fd_glob_55, "security/GUARDRAILS.md": __fd_glob_56, "security/PUBLIC_CREDS.md": __fd_glob_57, "security/ROUTE_GUARD_TIERS.md": __fd_glob_58, "security/SOCKET_DEV_FINDINGS.md": __fd_glob_59, "security/STEALTH_GUIDE.md": __fd_glob_60, }); \ No newline at end of file +export const docs = await create.docs("docs", "docs", {"meta.json": __fd_glob_61, "architecture/meta.json": __fd_glob_62, "compression/meta.json": __fd_glob_63, "frameworks/meta.json": __fd_glob_64, "guides/meta.json": __fd_glob_65, "ops/meta.json": __fd_glob_66, "reference/meta.json": __fd_glob_67, "reference/openapi.yaml": __fd_glob_68, "routing/meta.json": __fd_glob_69, "security/meta.json": __fd_glob_70, }, {"architecture/ARCHITECTURE.md": __fd_glob_0, "architecture/AUTHZ_GUIDE.md": __fd_glob_1, "architecture/CODEBASE_DOCUMENTATION.md": __fd_glob_2, "architecture/MONITORING_SECTIONS.md": __fd_glob_3, "architecture/REPOSITORY_MAP.md": __fd_glob_4, "architecture/RESILIENCE_GUIDE.md": __fd_glob_5, "compression/COMPRESSION_ENGINES.md": __fd_glob_6, "compression/COMPRESSION_GUIDE.md": __fd_glob_7, "compression/COMPRESSION_LANGUAGE_PACKS.md": __fd_glob_8, "compression/COMPRESSION_RULES_FORMAT.md": __fd_glob_9, "compression/RTK_COMPRESSION.md": __fd_glob_10, "guides/DOCKER_GUIDE.md": __fd_glob_11, "guides/ELECTRON_GUIDE.md": __fd_glob_12, "guides/FEATURES.md": __fd_glob_13, "guides/I18N.md": __fd_glob_14, "guides/KIRO_SETUP.md": __fd_glob_15, "guides/PWA_GUIDE.md": __fd_glob_16, "guides/SETUP_GUIDE.md": __fd_glob_17, "guides/TERMUX_GUIDE.md": __fd_glob_18, "guides/TROUBLESHOOTING.md": __fd_glob_19, "guides/UNINSTALL.md": __fd_glob_20, "guides/USER_GUIDE.md": __fd_glob_21, "frameworks/A2A-SERVER.md": __fd_glob_22, "frameworks/AGENT-SKILLS.md": __fd_glob_23, "frameworks/AGENTBRIDGE.md": __fd_glob_24, "frameworks/AGENT_PROTOCOLS_GUIDE.md": __fd_glob_25, "frameworks/CLOUD_AGENT.md": __fd_glob_26, "frameworks/EMBEDDED-SERVICES.md": __fd_glob_27, "frameworks/EVALS.md": __fd_glob_28, "frameworks/GAMIFICATION.md": __fd_glob_29, "frameworks/MCP-SERVER.md": __fd_glob_30, "frameworks/MEMORY.md": __fd_glob_31, "frameworks/OPENCODE.md": __fd_glob_32, "frameworks/SKILLS.md": __fd_glob_33, "frameworks/TRAFFIC_INSPECTOR.md": __fd_glob_34, "frameworks/WEBHOOKS.md": __fd_glob_35, "ops/COVERAGE_PLAN.md": __fd_glob_36, "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": __fd_glob_37, "ops/FLY_IO_DEPLOYMENT_GUIDE.md": __fd_glob_38, "ops/PROXY_GUIDE.md": __fd_glob_39, "ops/RELEASE_CHECKLIST.md": __fd_glob_40, "ops/SQLITE_RUNTIME.md": __fd_glob_41, "ops/TUNNELS_GUIDE.md": __fd_glob_42, "ops/VM_DEPLOYMENT_GUIDE.md": __fd_glob_43, "reference/API_REFERENCE.md": __fd_glob_44, "reference/CLI-TOOLS.md": __fd_glob_45, "reference/ENVIRONMENT.md": __fd_glob_46, "reference/FREE_TIERS.md": __fd_glob_47, "reference/PROVIDER_REFERENCE.md": __fd_glob_48, "routing/AUTO-COMBO.md": __fd_glob_49, "routing/QUOTA_SHARE.md": __fd_glob_50, "routing/REASONING_REPLAY.md": __fd_glob_51, "security/CLI_TOKEN.md": __fd_glob_52, "security/CLI_TOKEN_AUTH.md": __fd_glob_53, "security/COMPLIANCE.md": __fd_glob_54, "security/ERROR_SANITIZATION.md": __fd_glob_55, "security/GUARDRAILS.md": __fd_glob_56, "security/PUBLIC_CREDS.md": __fd_glob_57, "security/ROUTE_GUARD_TIERS.md": __fd_glob_58, "security/SOCKET_DEV_FINDINGS.md": __fd_glob_59, "security/STEALTH_GUIDE.md": __fd_glob_60, }); \ No newline at end of file diff --git a/bin/cli/commands/memory.mjs b/bin/cli/commands/memory.mjs index 052ed68c99..2e38acd761 100644 --- a/bin/cli/commands/memory.mjs +++ b/bin/cli/commands/memory.mjs @@ -3,7 +3,37 @@ import { apiFetch } from "../api.mjs"; import { emit } from "../output.mjs"; import { t } from "../i18n.mjs"; -const VALID_TYPES = ["user", "feedback", "project", "reference"]; +const VALID_TYPES = ["factual", "episodic", "procedural", "semantic"]; + +const LEGACY_TYPE_MAP = { + user: "factual", + feedback: "factual", + project: "factual", + reference: "factual", +}; + +/** + * Plan 21 Bug#4/D17 fix: remap legacy types in ALL CLI subcommands + * (search/list/clear in addition to add), with a stderr warning on remap. + * Returns the canonical type, or the original value (which the backend will + * 400 on if invalid) — never throws. + */ +function applyLegacyTypeMap(type) { + if (!type) return type; + if (Object.prototype.hasOwnProperty.call(LEGACY_TYPE_MAP, type)) { + const mapped = LEGACY_TYPE_MAP[type]; + process.stderr.write( + `Warning: legacy type '${type}' is deprecated; using '${mapped}'. Use --type factual|episodic|procedural|semantic.\n` + ); + return mapped; + } + if (!VALID_TYPES.includes(type)) { + process.stderr.write( + `Warning: unknown type '${type}'. Valid types: factual, episodic, procedural, semantic.\n` + ); + } + return type; +} function truncate(v, len = 60) { if (v == null) return "-"; @@ -53,7 +83,8 @@ async function confirm(question) { export async function runMemorySearch(query, opts, cmd) { const globalOpts = cmd.optsWithGlobals(); const params = new URLSearchParams({ q: query, limit: String(opts.limit ?? 20) }); - if (opts.type) params.set("type", opts.type); + const mappedSearchType = applyLegacyTypeMap(opts.type); + if (mappedSearchType) params.set("type", mappedSearchType); if (opts.apiKey) params.set("apiKey", opts.apiKey); if (opts.tokenBudget) params.set("tokenBudget", String(opts.tokenBudget)); const res = await apiFetch(`/api/memory?${params}`); @@ -72,9 +103,10 @@ export async function runMemoryAdd(opts, cmd) { process.stderr.write("--content or --file required\n"); process.exit(2); } + const resolvedType = opts.type ? applyLegacyTypeMap(opts.type) : "factual"; const body = { content, - type: opts.type ?? "user", + type: resolvedType, ...(opts.metadata ? { metadata: JSON.parse(opts.metadata) } : {}), ...(opts.apiKey ? { apiKey: opts.apiKey } : {}), }; @@ -94,7 +126,8 @@ export async function runMemoryClear(opts, cmd) { if (!ok) process.exit(0); } const params = new URLSearchParams(); - if (opts.type) params.set("type", opts.type); + const mappedClearType = applyLegacyTypeMap(opts.type); + if (mappedClearType) params.set("type", mappedClearType); if (opts.olderThan) { const iso = parseDuration(opts.olderThan); if (!iso) { @@ -112,7 +145,8 @@ export async function runMemoryClear(opts, cmd) { export async function runMemoryList(opts, cmd) { const globalOpts = cmd.optsWithGlobals(); const params = new URLSearchParams({ limit: String(opts.limit ?? 100) }); - if (opts.type) params.set("type", opts.type); + const mappedListType = applyLegacyTypeMap(opts.type); + if (mappedListType) params.set("type", mappedListType); if (opts.apiKey) params.set("apiKey", opts.apiKey); const res = await apiFetch(`/api/memory?${params}`); if (!res.ok) { diff --git a/docs/architecture/REPOSITORY_MAP.md b/docs/architecture/REPOSITORY_MAP.md index ea08057bcb..242598d597 100644 --- a/docs/architecture/REPOSITORY_MAP.md +++ b/docs/architecture/REPOSITORY_MAP.md @@ -127,6 +127,7 @@ src/ | `app/a2a/` | A2A JSON-RPC 2.0 entry point (`POST /a2a`) | | `app/.well-known/agent.json/` | A2A Agent Card (discovery) | | `app/(dashboard)/dashboard/` | Dashboard UI pages (~35 pages: providers, combos, settings, memory, skills, webhooks, evals, audit, batch, cache, costs, health, system, activity, etc.) | +| `app/(dashboard)/dashboard/memory/` | Memory Studio (plan 21): `page.tsx` (3-tab shell), `components/` (MemoryConceptCard, MemoryEngineStatus, EmbeddingSourceSelector, EditMemoryModal, RetrievePreview, QdrantConfigCard, RerankConfigCard), `components/tabs/` (MemoriesTab, PlaygroundTab, EngineTab), `hooks/` (useEngineStatus, useMemorySettings) | | `app/(dashboard)/dashboard/tools/agent-bridge/` | AgentBridge dashboard page — server card, 9 agent cards, setup wizard, model mapping, bypass list. i18n PT-BR + EN. See `docs/frameworks/AGENTBRIDGE.md`. | | `app/(dashboard)/dashboard/tools/traffic-inspector/` | Traffic Inspector dashboard page — DevTools split, 7 detail tabs, 4 capture mode toggles, session recorder, context colorization. i18n PT-BR + EN. See `docs/frameworks/TRAFFIC_INSPECTOR.md`. | | `app/(dashboard)/dashboard/activity/` | Activity feed page (Group B): `page.tsx` (server) + `ActivityFeedClient.tsx` + `components/{ActivityFeed,ActivityItem,DayHeader,EventTypeFilter}.tsx` — see `docs/architecture/MONITORING_SECTIONS.md` | @@ -164,7 +165,10 @@ src/ | `evals/` | Eval framework (suites, runner, runtime) — see `docs/frameworks/EVALS.md` | | `guardrails/` | PII masker, prompt injection, vision bridge — see `docs/security/GUARDRAILS.md` | | `jobs/` | Background jobs (cron-like) | -| `memory/` | Conversational memory (SQLite FTS5 + Qdrant) — see `docs/frameworks/MEMORY.md` | +| `memory/` | Conversational memory (SQLite FTS5 + sqlite-vec hybrid RRF + Qdrant tier 2) — see `docs/frameworks/MEMORY.md` | +| `memory/embedding/` | Multi-source embedding layer: `index.ts` (resolver), `remote.ts`, `staticPotion.ts`, `transformersLocal.ts`, `cache.ts`, `types.ts` (plan 21) | +| `memory/vectorStore.ts` | sqlite-vec v0.1.9 wrapper — KNN brute-force + hybrid RRF (FTS5 + vector, k=60). Lazy-init, degrades gracefully when sqlite-vec unavailable. (plan 21) | +| `memory/reindex.ts` | `runReindexBatch()` — processes memories with `needs_reindex=1` in background; called by `POST /api/memory/reindex` and lazy-backfill path. (plan 21) | | `monitoring/` | Health checks, metrics emission | | `oauth/` | OAuth flows for 14 providers (claude, codex, antigravity, cursor, github, gemini, kimi-coding, kilocode, cline, qwen, kiro, qoder, gitlab-duo, windsurf) | | `plugins/` | Plugin registry | @@ -183,7 +187,8 @@ src/ | Subdir | Purpose | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `db/core.ts` | `getDbInstance()` singleton with WAL journaling | -| `db/migrations/` | 55 versioned SQL files (idempotent, transactional, numbered `001`..`055`) | +| `db/migrations/` | Versioned SQL files (idempotent, transactional). `073_memory_vec.sql` adds `memory_vec_meta` + `needs_reindex` column (plan 21). | +| `db/memoryVec.ts`| CRUD for `memory_vec_meta` (active_dim, embedding_signature, last_reset_at, vec_loaded) + `markMemoryNeedsReindex`, `getMemoryReindexQueue`, etc. (plan 21) | | `db/.ts` | One module per domain: providers, combos, apiKeys, users, sessions, usage, audit*log, webhooks, skills, memory_entries, cloud_agent_tasks, evals*\*, reasoning_cache, etc. | ### `src/domain/` diff --git a/docs/frameworks/MEMORY.md b/docs/frameworks/MEMORY.md index 07b226fcae..018a551ed5 100644 --- a/docs/frameworks/MEMORY.md +++ b/docs/frameworks/MEMORY.md @@ -1,13 +1,13 @@ --- title: "Memory System" -version: 3.8.2 -lastUpdated: 2026-05-13 +version: 3.8.6 +lastUpdated: 2026-05-28 --- # Memory System > **Source of truth:** `src/lib/memory/` and `src/app/api/memory/` -> **Last updated:** 2026-05-13 — v3.8.0 +> **Last updated:** 2026-05-28 — v3.8.6 (plan 21 — Memory Engine Redesign) OmniRoute provides persistent conversational memory keyed by API key (and optionally session id). Memories are extracted automatically from LLM responses @@ -27,17 +27,143 @@ Client → /v1/chat/completions (apiKeyInfo resolved upstream) → resolveMemoryOwnerId(apiKeyInfo) # extracts id → getMemorySettings() # cached settings → shouldInjectMemory(body, {enabled}) # gate - → retrieveMemories(apiKeyId, config) # SQL + optional FTS5 + → retrieveMemories(apiKeyId, config) # SQL + FTS5 + optional vector → injectMemory(body, memories, provider) # system or user message → upstream provider call → on response: extractFacts(text, apiKeyId, sessionId) # non-blocking → setImmediate → createMemory(fact) per match + → embed(content) + upsertVector(id, vec) ``` The injection and extraction call-sites are wired in `open-sse/handlers/chatCore.ts` (look for `retrieveMemories`, `injectMemory`, and `extractFacts`). +## Engine architecture (3-tier resolution) + +The Memory Engine resolves the retrieval path at runtime based on available +infrastructure and settings. Three tiers exist, applied in priority order: + +``` + ┌─────────────────────────────────────────────────────────────┐ + │ TIER 0 — Keyword (FTS5) │ + │ Always available. SQLite FTS5 full-text search over │ + │ content + key. Used when strategy = "exact" or as fallback. │ + └──────────────────────────────────┬──────────────────────────┘ + │ strategy = semantic|hybrid? + ▼ + ┌─────────────────────────────────────────────────────────────┐ + │ TIER 1 — Embedded Vector (sqlite-vec) │ + │ sqlite-vec v0.1.9 loaded via db.loadExtension(). │ + │ KNN brute-force over Float32 vectors. Active when: │ + │ • sqlite-vec loadExtension succeeds │ + │ • An embedding source is available (remote | static | │ + │ transformers) that can produce a Float32Array │ + │ • vec_memories table exists (created on first ready()) │ + └──────────────────────────────────┬──────────────────────────┘ + │ qdrant.enabled? + ▼ + ┌─────────────────────────────────────────────────────────────┐ + │ TIER 2 — Qdrant (opt-in external vector database) │ + │ When enabled, replaces sqlite-vec for semantic/hybrid. │ + │ Requires running Qdrant instance + configured host/port. │ + └─────────────────────────────────────────────────────────────┘ +``` + +Degradation is automatic and transparent: +- If sqlite-vec fails to load, tier 1 is unavailable → falls back to tier 0. +- If embedding source returns an error, tier 1 falls back to tier 0. +- If Qdrant is unhealthy, tier 2 falls back to tier 1 (or tier 0 if tier 1 + is also unavailable). + +## Embedding sources + +The embedding layer (`src/lib/memory/embedding/`) resolves which source to use +based on `MemorySettingsExtended.embeddingSource`: + +| Source | Description | Key required | Cold start | +| -------------- | ------------------------------------------------------------------------------- | ------------ | ---------- | +| `remote` | Uses a configured provider's embedding API (OpenAI, Cohere, etc.) | Yes | None | +| `static` | Local lookup-table embedding via `potion-base-8M` (WordPiece + mean pooling) | No | ~200ms | +| `transformers` | Local ONNX inference via `@huggingface/transformers` v4, `all-MiniLM-L6-v2` | No | ~3s + ~400MB RAM | +| `auto` | Runtime resolution: remote (if key exists) → static → transformers → null | Depends | Depends | + +**Resolution order for `auto`:** +1. Find first provider in `listEmbeddingProviders()` with `hasKey === true` → `remote`. +2. If `settings.staticEnabled === true` → `static`. +3. If `settings.transformersEnabled === true` → `transformers`. +4. Otherwise → `null` (degrades to FTS5 keyword search). + +The embedding cache (`src/lib/memory/embedding/cache.ts`) uses an in-memory +LRU map keyed by `${source}:${model}:${dim}:${sha256(text)}`, capped at +`MEMORY_EMBEDDING_CACHE_MAX` entries (default 1000) with a TTL of +`MEMORY_EMBEDDING_CACHE_TTL_MS` (default 5 min). Shared across all callers +per process lifecycle. + +## Hybrid RRF (k=60) + +When `strategy = "hybrid"` and the vector store is available, retrieval uses +Reciprocal Rank Fusion to merge FTS5 and vector results: + +``` +RRF(d) = Σ 1 / (k + rank_i(d)) where k = 60 (configurable via MEMORY_RRF_K) + i +``` + +Concretely: +1. Run FTS5 search → ranked list `R_fts` (position 1..N). +2. Run KNN vector search → ranked list `R_vec` (position 1..M). +3. For each unique `memoryId`: + `rrf_score = 1/(60 + fts_rank)` + `1/(60 + vec_rank)` (0 if not in list). +4. Sort by `rrf_score` DESC, apply token budget walk. + +RRF is well-known to be effective without needing score normalization across +heterogeneous retrieval systems. The default `k=60` is from the original +Cormack et al. paper and works well for small corpora (<10k memories). + +## Backfill (lazy + reindex) + +When the embedding model changes (detected via `embedding_signature`), the +vector store is rebuilt and all existing memories are marked +`needs_reindex = 1` in the `memories` table. + +**Lazy backfill**: On the next retrieval, any memory missing a vector entry is +embedded and inserted into `vec_memories` before the search runs. This +amortizes the backfill cost across real requests without blocking startup. + +**Explicit reindex**: The Engine tab in `/dashboard/memory` provides a +"Reindex Now" button that calls `POST /api/memory/reindex`. The handler calls +`runReindexBatch()` from `src/lib/memory/reindex.ts`, which processes up to +`limit` pending entries per request. Progress can be polled via +`GET /api/memory/engine-status` (`vectorStore.needsReindex`). + +The `memory_vec_meta` table (migration `073_memory_vec.sql`) stores: +- `active_dim` — current vector dimension (null = not yet calibrated). +- `embedding_signature` — `${source}:${model}:${dim}` used to detect changes. +- `last_reset_at` — timestamp of last full reset. +- `vec_loaded` — 0/1 flag whether sqlite-vec loaded successfully. + +## Settings extension + +Seven new fields were added to `MemorySettingsExtended` (plan 21, D9) in +`src/shared/schemas/memory.ts`, persisted via `src/lib/db/settings.ts`: + +| Field | Type | Default | Description | +| ---------------------- | --------------------------------------------- | ------------ | --------------------------------------------- | +| `embeddingSource` | `"remote" \| "static" \| "transformers" \| "auto"` | `"auto"` | Which embedding source to use | +| `embeddingProviderModel` | `string \| null` | `null` | Provider/model in `provider/model` format | +| `transformersEnabled` | `boolean` | `false` | Opt-in for Transformers.js (MiniLM, ~400MB) | +| `staticEnabled` | `boolean` | `false` | Opt-in for static potion-base-8M local model | +| `rerankEnabled` | `boolean` | `false` | Enable reranking step (adds +200-500ms/req) | +| `rerankProviderModel` | `string \| null` | `null` | Rerank provider/model in `provider/model` format | +| `vectorStore` | `"sqlite-vec" \| "qdrant" \| "auto"` | `"auto"` | Which vector backend to use | + +These are exposed via `GET /PUT /api/settings/memory` (schema `MemorySettingsExtendedSchema`). + +> **TODO (D20):** Scope `global` (sharing memories across all API keys) is not +> implemented in this release. It requires schema changes and a global retrieval +> path. Track separately. + ## Storage Layers ### Primary: SQLite (`memories` table) @@ -78,10 +204,10 @@ Used by `retrieval.ts` for the `semantic` and `hybrid` strategies (see below). The retrieval code guards with `hasTable("memory_fts")` and falls back to chronological order if the FTS table is missing or the FTS query throws. -### Optional: Qdrant (vector store) +### Optional: Qdrant (vector store tier 2) -`src/lib/memory/qdrant.ts` implements an optional Qdrant integration for true -semantic memory: +`src/lib/memory/qdrant.ts` implements an optional Qdrant integration as tier 2 +vector store. Enabled via `qdrantEnabled` in settings / toggle in Engine tab. - `upsertSemanticMemoryPoint()` — embed `key + content` with the configured embedding model, ensure the collection exists (creates cosine-distance @@ -90,24 +216,24 @@ apiKeyId, sessionId, key, content, metadata, createdAtUnix, expiresAtUnix}`. - `searchSemanticMemory(query, topK, scope)` — embed the query, search the collection filtered by `kind = "omniroute_memory"` and optionally by `apiKeyId` / `sessionId`. Caps `topK` to `[1, 20]`. -- `deleteSemanticMemoryPoint(id)` — single point delete. +- `deleteSemanticMemoryPoint(id)` — single point delete. Called by + `deleteMemory()` after the SQLite row is removed (D15). - `cleanupSemanticMemoryPoints({retentionDays})` — bulk delete points whose `expiresAtUnix` is in the past or whose `createdAtUnix` is older than the retention cutoff. Counts first so the dashboard can show actual numbers. - `checkQdrantHealth()` — `GET /readyz` health probe with latency. -> **TODO**: The chat pipeline (`chatCore.ts`) and the in-tree `retrieveMemories()` -> implementation do not currently call `upsertSemanticMemoryPoint` or -> `searchSemanticMemory`. The Qdrant integration is feature-flagged via -> `qdrantEnabled` in settings, but at the time of writing the -> `searchSemanticMemory` results are not fused into retrieval — the -> `semantic`/`hybrid` retrieval strategies use SQLite FTS5 only. The settings UI -> in `dashboard/settings → MemorySkillsTab` exposes Qdrant config, health, -> search test, and cleanup, but the corresponding `/api/settings/qdrant`, -> `/api/settings/qdrant/health`, `/api/settings/qdrant/search`, and -> `/api/settings/qdrant/cleanup` routes are referenced from the UI but **not -> present** under `src/app/api/settings/qdrant/` (only `embedding-models/` is -> wired). Treat Qdrant as preview/optional plumbing. +The settings UI exposes Qdrant config, health check, semantic search test, +and cleanup in the **Engine tab** of `/dashboard/memory`. The corresponding +routes under `src/app/api/settings/qdrant/` are all wired as of v3.8.6: + +| Route | Method | Description | +| ----- | ------ | ----------- | +| `/api/settings/qdrant` | `GET` / `PUT` | Read / update Qdrant settings | +| `/api/settings/qdrant/health` | `GET` | Liveness probe + latency | +| `/api/settings/qdrant/search` | `POST` | Semantic search test | +| `/api/settings/qdrant/cleanup` | `POST` | Remove expired / old points | +| `/api/settings/qdrant/embedding-models` | `GET` | List available embedding models | ## Memory Types @@ -202,6 +328,8 @@ Memory configuration is **stored in the DB settings table**, not in env vars. in-process; `invalidateMemorySettingsCache()` is called by the settings PUT route after writes. +### Legacy fields (all versions) + | DB key | Type | Default | UI control | | --------------------- | ------- | -------------------------------------------------- | ----------------------------------------------- | | `memoryEnabled` | boolean | `true` | Memory on/off | @@ -213,14 +341,38 @@ route after writes. Note: the UI strategy `"recent"` maps to the internal `"exact"` retrieval strategy via `toMemoryRetrievalConfig()` (chronological order). +### New fields (v3.8.6, plan 21 D9) + +See also the "Settings extension" section above for field descriptions. + +| DB key | API field | Default | +| ------------------------- | ---------------------- | ------------- | +| `memoryEmbeddingSource` | `embeddingSource` | `"auto"` | +| `memoryEmbeddingModel` | `embeddingProviderModel` | `null` | +| `memoryTransformersEnabled` | `transformersEnabled` | `false` | +| `memoryStaticEnabled` | `staticEnabled` | `false` | +| `memoryRerankEnabled` | `rerankEnabled` | `false` | +| `memoryRerankModel` | `rerankProviderModel` | `null` | +| `memoryVectorStore` | `vectorStore` | `"auto"` | + Qdrant-related DB keys (`qdrantEnabled`, `qdrantHost`, `qdrantPort`, `qdrantApiKey`, `qdrantCollection` default `"omniroute_memory"`, `qdrantEmbeddingModel` default `"openai/text-embedding-3-small"`) are read by `normalizeQdrantConfig()` in `qdrant.ts`. -No `MEMORY_*` or `QDRANT_*` env vars exist today — everything is per-instance -DB settings. `OMNIROUTE_MEMORY_MB` (commented out in `.env.example`) is -unrelated and refers to Node heap sizing. +### Environment variables (v3.8.6) + +Six optional env vars tune the engine's runtime behaviour (documented in `.env.example`): + +| Variable | Default | Description | +| ------------------------------- | ------- | -------------------------------------------------- | +| `MEMORY_EMBEDDING_CACHE_TTL_MS` | `300000` | Embedding cache TTL (5 min) | +| `MEMORY_EMBEDDING_CACHE_MAX` | `1000` | Max entries in embedding LRU cache | +| `MEMORY_TRANSFORMERS_MODEL` | `Xenova/all-MiniLM-L6-v2` | HF repo for Transformers.js model | +| `MEMORY_STATIC_MODEL` | `minishlab/potion-base-8M` | HF repo for static potion model | +| `MEMORY_STATIC_CACHE_DIR` | `/embeddings` | Where to store downloaded models | +| `MEMORY_VEC_TOP_K` | `20` | Default top-K for vector search | +| `MEMORY_RRF_K` | `60` | RRF k constant for hybrid search | ## Summarisation (`summarization.ts`) @@ -240,15 +392,39 @@ loss is one-way: original text is overwritten. All endpoints require management auth (`requireManagementAuth`). -| Method | Path | Description | -| -------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `GET` | `/api/memory` | Paginated list with filters: `apiKeyId`, `type`, `sessionId`, `q`, `limit`, `page`, `offset`. Response includes `stats.total` and `stats.byType` | -| `POST` | `/api/memory` | Create entry (Zod-validated: `content`, `key`, optional `type`, `sessionId`, `apiKeyId`, `metadata`, `expiresAt`). Calls `createMemory()` which upserts on `(apiKeyId, key)` | -| `GET` | `/api/memory/[id]` | Fetch a single entry by UUID | -| `DELETE` | `/api/memory/[id]` | Delete an entry; returns 404 when missing | -| `GET` | `/api/memory/health` | Runs `verifyExtractionPipeline("health-check")` — round-trip create→list→delete to confirm the store is alive. Returns `{working, latencyMs, error?}` | -| `GET` | `/api/settings/memory` | Current normalised `MemorySettings` | -| `PUT` | `/api/settings/memory` | Update one or more of `enabled`, `maxTokens`, `retentionDays`, `strategy`, `skillsEnabled` | +### Core memory endpoints (existing + updated) + +| Method | Path | Description | +| ---------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `GET` | `/api/memory` | Paginated list with filters: `apiKeyId`, `type`, `sessionId`, `q`, `limit`, `page`, `offset`. Response includes `stats.total`, `stats.tokensUsed`, `stats.hitRate`, `cacheStats` | +| `POST` | `/api/memory` | Create entry (Zod-validated: `content`, `key`, optional `type`, `sessionId`, `apiKeyId`, `metadata`, `expiresAt`). Calls `createMemory()` which upserts on `(apiKeyId, key)` | +| `GET` | `/api/memory/[id]` | Fetch a single entry by UUID | +| `PUT` | `/api/memory/[id]` | Update entry fields (`type`, `key`, `content`, `metadata`). Body: `MemoryUpdatePutSchema`. Also syncs vector if embedding source available. | +| `DELETE` | `/api/memory/[id]` | Delete an entry; also deletes from `vec_memories` (D15) and Qdrant best-effort. Returns 404 when missing. | +| `GET` | `/api/memory/health` | Runs `verifyExtractionPipeline("health-check")` — round-trip create→list→delete. Returns `{working, latencyMs, error?}` | + +### New memory engine endpoints (plan 21) + +| Method | Path | Description | +| -------- | ---------------------------------- | --------------------------------------------------------------------------------------- | +| `POST` | `/api/memory/retrieve-preview` | Dry-run of `retrieveMemories` — returns ranked results with score, tier, tokens. Body: `RetrievePreviewSchema`. Does NOT inject or modify memories. | +| `GET` | `/api/memory/embedding-providers` | Lists providers with embedding models, indicating which have a configured API key. | +| `GET` | `/api/memory/engine-status` | Returns full engine status: keyword tier, embedding resolution, vector store stats, Qdrant health, rerank config. Shape: `MemoryEngineStatusSchema`. | +| `POST` | `/api/memory/summarize` | Manually trigger memory compaction. Body: `MemorySummarizeSchema` (`olderThanDays`, `apiKeyId?`, `dryRun`). Returns `{candidates, tokensSaved}`. | +| `POST` | `/api/memory/reindex` | Trigger vector reindex for memories with `needs_reindex=1`. Body: `MemoryReindexSchema` (`force`). Returns `{started, pending}`. | + +### Settings endpoints + +| Method | Path | Description | +| -------- | ---------------------------------- | ------------------------------------------------------------------------ | +| `GET` | `/api/settings/memory` | Current normalised `MemorySettingsExtended` (7 new fields + legacy) | +| `PUT` | `/api/settings/memory` | Update any field from `MemorySettingsExtendedSchema` (12 total fields) | +| `GET` | `/api/settings/qdrant` | Current Qdrant settings (`QdrantSettingsSchema`) | +| `PUT` | `/api/settings/qdrant` | Update Qdrant settings. Body: `QdrantSettingsUpdateSchema`. `apiKey` = empty string removes key. | +| `GET` | `/api/settings/qdrant/health` | Liveness probe against configured Qdrant instance. Returns `QdrantHealthResultSchema`. | +| `POST` | `/api/settings/qdrant/search` | Semantic search test against Qdrant. Body: `QdrantSearchSchema` (`query`, `topK`). | +| `POST` | `/api/settings/qdrant/cleanup` | Remove Qdrant points for expired / old memories. | +| `GET` | `/api/settings/qdrant/embedding-models` | List embedding models available for Qdrant. | The `/api/memory` list query supports either `page`-based pagination (`parsePaginationParams`) **or** raw `offset` — when `offset` is present it @@ -259,31 +435,55 @@ takes precedence and a derived `page` is computed for the response shape. When the MCP server is enabled, three memory tools are registered: - `omniroute_memory_search` — `{apiKeyId, query?, type?, maxTokens?, limit?}` - → wraps `retrieveMemories()` with `retrievalStrategy: "exact"`, optionally - filters by `type`, and reports `totalTokens`. + → wraps `retrieveMemories()`. As of v3.8.6 (D16), the `strategy` is read + from `getMemorySettings()` instead of being hardcoded to `"exact"`. If + `query` is provided and `strategy` is `semantic` or `hybrid`, the vector + store is used when available. - `omniroute_memory_add` — `{apiKeyId, sessionId?, type, key, content, -metadata?}` → wraps `createMemory()`. +metadata?}` → wraps `createMemory()`. Accepts only the 4 canonical types: + `factual`, `episodic`, `procedural`, `semantic` (D17). - `omniroute_memory_clear` — `{apiKeyId, type?, olderThan?}` → lists matching entries, optionally filters by created-before timestamp, then deletes each - via `deleteMemory()`. + via `deleteMemory()` (which also removes vectors from sqlite-vec + Qdrant). See [MCP-SERVER.md](./MCP-SERVER.md) for transport and scope details. -## Dashboard +## Dashboard (Memory Studio) -`src/app/(dashboard)/dashboard/memory/page.tsx` provides: +`src/app/(dashboard)/dashboard/memory/page.tsx` is now a **3-tab Studio**: +### Tab: Memórias / Memories +- Concept card (collapsible "How it works" explainer). - Real-time list, search, and pagination (debounced 300 ms). - Type filter (`factual` / `episodic` / `procedural` / `semantic` / all). - Add-memory modal (key, content, type). -- Delete per row. +- Inline edit (pencil button → `PUT /api/memory/[id]`). +- Delete per row (with confirmation dialog). - JSON export of the current page; JSON import via file picker. +- Stat cards: `totalEntries`, `tokensUsed`, `hitRate`. +- "Compact old" button → `POST /api/memory/summarize` (dry-run first shows + candidate count, then confirms). - A green/red health dot driven by `GET /api/memory/health`. -- Stat cards: `totalEntries`, `tokensUsed`, `hitRate` (the latter two come - from the API stats payload). -Memory and Qdrant settings live under -`/dashboard/settings → Memory & Skills` (`MemorySkillsTab.tsx`). +### Tab: Playground +- Query input + strategy selector (Exact / Semantic / Hybrid) + token budget. +- "Simulate" → `POST /api/memory/retrieve-preview` — shows ranked results with + `score`, `tier`, `tokens`, `vecScore`, `ftsScore`. +- Resolution panel showing which embedding source / vector store was used and + whether a fallback occurred. + +### Tab: Engine +- Engine status panel (keyword FTS5 chip, embedding chip, vector store chip, + Qdrant health chip, rerank chip). +- "Reindex Now" button → `POST /api/memory/reindex`. +- Embedding source selector (auto / remote / static / transformers + toggles). +- Qdrant config card (enable toggle, host/port/collection/key, test connection, + semantic search test, cleanup). +- Rerank config card (enable toggle, provider/model selector). + +Memory and Qdrant settings also live under +`/dashboard/settings → Memory & Skills` (`MemorySkillsTab.tsx`) for +the legacy/global settings surface. ## Caching @@ -316,12 +516,28 @@ default TTL 5 min). - [API_REFERENCE.md](../reference/API_REFERENCE.md) — broader API surface. - Source modules: - `src/lib/memory/types.ts`, `schemas.ts` - - `src/lib/memory/store.ts`, `retrieval.ts`, `injection.ts` + - `src/lib/memory/store.ts`, `retrieval.ts`, `injection.ts`, `reindex.ts` - `src/lib/memory/extraction.ts`, `summarization.ts`, `verify.ts` - `src/lib/memory/settings.ts`, `qdrant.ts`, `cache.ts` + - `src/lib/memory/vectorStore.ts` — sqlite-vec + hybrid RRF + - `src/lib/memory/embedding/index.ts` — multi-source embedding layer + - `src/lib/memory/embedding/types.ts`, `remote.ts`, `staticPotion.ts`, + `transformersLocal.ts`, `cache.ts` + - `src/shared/schemas/memory.ts` — Zod schemas for all memory API bodies + - `src/shared/schemas/qdrant.ts` — Zod schemas for Qdrant settings/ops + - `src/lib/db/memoryVec.ts` — CRUD for `memory_vec_meta` - `src/lib/db/migrations/015_create_memories.sql`, - `022_add_memory_fts5.sql`, `023_fix_memory_fts_uuid.sql` + `022_add_memory_fts5.sql`, `023_fix_memory_fts_uuid.sql`, + `073_memory_vec.sql` - `src/app/api/memory/route.ts`, `[id]/route.ts`, `health/route.ts` + - `src/app/api/memory/retrieve-preview/route.ts` + - `src/app/api/memory/engine-status/route.ts` + - `src/app/api/memory/embedding-providers/route.ts` + - `src/app/api/memory/summarize/route.ts` + - `src/app/api/memory/reindex/route.ts` - `src/app/api/settings/memory/route.ts` + - `src/app/api/settings/qdrant/route.ts` + sub-routes + - `src/app/(dashboard)/dashboard/memory/` — Studio UI (page + components + + tabs + hooks) - `open-sse/handlers/chatCore.ts` (injection / extraction wiring) - `open-sse/mcp-server/tools/memoryTools.ts` diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index c11779d0d7..87056a5dcf 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -633,6 +633,21 @@ The logging system writes to both stdout and rotated log files. All configuratio | ------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------- | | `OMNIROUTE_RTK_TRUST_PROJECT_FILTERS` | unset | Trust project `.rtk/filters.json` without a `.rtk/trust.json` hash. Use only in controlled local development. | +### Memory Engine (plan 21) + +Embedding layer, vector store and reranking knobs for the persistent memory subsystem (`src/lib/memory/`). + +| Variable | Default | Description | +| ------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------- | +| `MEMORY_EMBEDDING_CACHE_TTL_MS` | `300000` (5 min) | TTL for the in-memory embedding cache (per source/model/dim signature). | +| `MEMORY_EMBEDDING_CACHE_MAX` | `1000` | Max LRU entries kept in the embedding cache. | +| `MEMORY_TRANSFORMERS_MODEL` | `Xenova/all-MiniLM-L6-v2` | HF repo id for the opt-in `@huggingface/transformers` local MiniLM pipeline (~23 MB int8, ~400 MB RAM). | +| `MEMORY_STATIC_MODEL` | `minishlab/potion-base-8M` | HF repo id for the static potion/Model2Vec lookup-table embedder. Downloaded lazily into the cache dir. | +| `MEMORY_STATIC_CACHE_DIR` | `/embeddings` | Directory used to cache the static potion model files. Defaults under `DATA_DIR` when unset. | +| `MEMORY_VEC_TOP_K` | `20` | Default top-K used by the `sqlite-vec` brute-force vector search inside `src/lib/memory/vectorStore.ts`. | +| `MEMORY_RRF_K` | `60` | Reciprocal Rank Fusion constant `k` for hybrid FTS5 + vector retrieval (sqlite-vec recipe). | +| `HF_HUB_ENDPOINT` | `https://huggingface.co` | Override Hugging Face Hub base URL used by `staticPotion.ts` (e.g. mirror endpoint for air-gapped setups). | + ### Low-RAM Docker Example ```bash diff --git a/docs/reference/openapi.yaml b/docs/reference/openapi.yaml index 87b0756f3a..9d4ffc0e40 100644 --- a/docs/reference/openapi.yaml +++ b/docs/reference/openapi.yaml @@ -23,6 +23,8 @@ servers: description: Local development tags: + - name: Memory + description: Conversational memory management — CRUD, engine status, playground preview, summarization, reindex, and Qdrant settings (plan 21 — v3.8.6). All routes require management auth. - name: Chat description: OpenAI-compatible chat completions - name: Messages @@ -94,6 +96,814 @@ tags: See docs/frameworks/TRAFFIC_INSPECTOR.md. paths: + # --- Memory Engine (plan 21) --- + /api/memory: + get: + tags: + - Memory + summary: List memory entries + security: + - ManagementSessionAuth: [] + parameters: + - name: apiKeyId + in: query + schema: + type: string + - name: type + in: query + schema: + type: string + enum: + - factual + - episodic + - procedural + - semantic + - name: sessionId + in: query + schema: + type: string + - name: q + in: query + schema: + type: string + - name: limit + in: query + schema: + type: integer + minimum: 1 + maximum: 200 + default: 50 + - name: page + in: query + schema: + type: integer + minimum: 1 + default: 1 + - name: offset + in: query + schema: + type: integer + minimum: 0 + responses: + '200': + description: Paginated list of memories with stats + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/MemoryEntry' + total: + type: integer + totalPages: + type: integer + stats: + type: object + properties: + total: + type: integer + tokensUsed: + type: integer + hitRate: + type: number + cacheStats: + type: object + properties: + hits: + type: integer + misses: + type: integer + '401': + $ref: '#/components/responses/Unauthorized' + post: + tags: + - Memory + summary: Create a memory entry + security: + - ManagementSessionAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - content + - key + properties: + content: + type: string + minLength: 1 + key: + type: string + minLength: 1 + type: + type: string + enum: + - factual + - episodic + - procedural + - semantic + default: factual + sessionId: + type: string + nullable: true + apiKeyId: + type: string + metadata: + type: object + additionalProperties: true + expiresAt: + type: string + format: date-time + nullable: true + responses: + '201': + description: Created memory entry + content: + application/json: + schema: + $ref: '#/components/schemas/MemoryEntry' + '400': + description: Validation error + '401': + $ref: '#/components/responses/Unauthorized' + /api/memory/{id}: + parameters: + - name: id + in: path + required: true + schema: + type: string + description: Memory UUID + get: + tags: + - Memory + summary: Get a single memory entry + security: + - ManagementSessionAuth: [] + responses: + '200': + description: Memory entry + content: + application/json: + schema: + $ref: '#/components/schemas/MemoryEntry' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Memory not found + put: + tags: + - Memory + summary: Update a memory entry + description: Update `type`, `key`, `content`, and/or `metadata` of an existing memory. If an embedding source is available, the vector in `vec_memories` is also regenerated. Corresponds to `MemoryUpdatePutSchema`. + security: + - ManagementSessionAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + type: + type: string + enum: + - factual + - episodic + - procedural + - semantic + key: + type: string + minLength: 1 + content: + type: string + minLength: 1 + metadata: + type: object + additionalProperties: true + additionalProperties: false + responses: + '200': + description: Updated memory entry + content: + application/json: + schema: + $ref: '#/components/schemas/MemoryEntry' + '400': + description: Validation error + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Memory not found + delete: + tags: + - Memory + summary: Delete a memory entry + description: Deletes the SQLite row, removes the vector from `vec_memories`, and best-effort deletes the point from Qdrant. + security: + - ManagementSessionAuth: [] + responses: + '200': + description: Deleted + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Memory not found + /api/memory/health: + get: + tags: + - Memory + summary: Memory store health check + description: Round-trip create→list→delete to verify the store is alive. + security: + - ManagementSessionAuth: [] + responses: + '200': + description: Health result + content: + application/json: + schema: + type: object + properties: + working: + type: boolean + latencyMs: + type: number + error: + type: string + nullable: true + '401': + $ref: '#/components/responses/Unauthorized' + /api/memory/retrieve-preview: + post: + tags: + - Memory + summary: Dry-run memory retrieval (Playground) + description: Simulates `retrieveMemories()` for a given query and returns the ranked results with score, tier, and token count. Does NOT modify any memory. Corresponds to `RetrievePreviewSchema`. + security: + - ManagementSessionAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - query + properties: + query: + type: string + minLength: 1 + strategy: + type: string + enum: + - exact + - semantic + - hybrid + default: hybrid + maxTokens: + type: integer + minimum: 1 + maximum: 16000 + default: 2000 + apiKeyId: + type: string + description: Optional — tests global pool when omitted + limit: + type: integer + minimum: 1 + maximum: 100 + default: 20 + additionalProperties: false + responses: + '200': + description: Preview results + content: + application/json: + schema: + type: object + properties: + memories: + type: array + items: + type: object + properties: + id: + type: string + type: + type: string + enum: + - factual + - episodic + - procedural + - semantic + key: + type: string + content: + type: string + score: + type: number + tokens: + type: integer + tier: + type: string + enum: + - fts5 + - vector + - hybrid-rrf + - qdrant + vecScore: + type: number + nullable: true + ftsScore: + type: number + nullable: true + resolution: + type: object + properties: + embeddingSource: + type: string + enum: + - remote + - static + - transformers + nullable: true + embeddingModel: + type: string + nullable: true + vectorStore: + type: string + enum: + - sqlite-vec + - qdrant + - none + strategyUsed: + type: string + enum: + - exact + - semantic + - hybrid + rerankApplied: + type: boolean + fallbackReason: + type: string + nullable: true + totalTokensUsed: + type: integer + budgetMaxTokens: + type: integer + '400': + description: Validation error + '401': + $ref: '#/components/responses/Unauthorized' + /api/memory/embedding-providers: + get: + tags: + - Memory + summary: List embedding providers + description: Returns all providers that have embedding-capable models, indicating which have an active API key configured. + security: + - ManagementSessionAuth: [] + responses: + '200': + description: Provider list + content: + application/json: + schema: + type: object + properties: + providers: + type: array + items: + type: object + properties: + provider: + type: string + hasKey: + type: boolean + models: + type: array + items: + type: object + properties: + id: + type: string + description: 'Format: provider/model' + name: + type: string + dimensions: + type: integer + nullable: true + '401': + $ref: '#/components/responses/Unauthorized' + /api/memory/engine-status: + get: + tags: + - Memory + summary: Memory engine status + description: Returns the full engine status including keyword tier availability, embedding resolution, vector store statistics (sqlite-vec), Qdrant health, and rerank configuration. Corresponds to `MemoryEngineStatusSchema`. + security: + - ManagementSessionAuth: [] + responses: + '200': + description: Engine status + content: + application/json: + schema: + type: object + properties: + keyword: + type: object + properties: + available: + type: boolean + backend: + type: string + enum: + - FTS5 + embedding: + type: object + properties: + source: + type: string + enum: + - remote + - static + - transformers + nullable: true + model: + type: string + nullable: true + dimensions: + type: integer + nullable: true + available: + type: boolean + reason: + type: string + cacheStats: + type: object + properties: + hits: + type: integer + misses: + type: integer + size: + type: integer + vectorStore: + type: object + properties: + backend: + type: string + enum: + - sqlite-vec + - qdrant + - none + available: + type: boolean + rowCount: + type: integer + needsReindex: + type: integer + reason: + type: string + qdrant: + type: object + properties: + enabled: + type: boolean + healthy: + type: boolean + nullable: true + latencyMs: + type: number + nullable: true + error: + type: string + nullable: true + rerank: + type: object + properties: + enabled: + type: boolean + provider: + type: string + nullable: true + model: + type: string + nullable: true + available: + type: boolean + reason: + type: string + '401': + $ref: '#/components/responses/Unauthorized' + /api/memory/summarize: + post: + tags: + - Memory + summary: Compact old memories + description: 'Manually triggers memory compaction for memories older than `olderThanDays`. Use `dryRun: true` to preview candidates. Corresponds to `MemorySummarizeSchema`.' + security: + - ManagementSessionAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + olderThanDays: + type: integer + minimum: 1 + maximum: 365 + default: 30 + apiKeyId: + type: string + description: Optional — compacts all keys when omitted + dryRun: + type: boolean + default: false + additionalProperties: false + responses: + '200': + description: Summarization result + content: + application/json: + schema: + type: object + properties: + candidates: + type: integer + tokensSaved: + type: integer + dryRun: + type: boolean + '400': + description: Validation error + '401': + $ref: '#/components/responses/Unauthorized' + /api/memory/reindex: + post: + tags: + - Memory + summary: Trigger vector reindex + description: 'Starts background reindexing of memories with `needs_reindex = 1`. Use `force: true` to regenerate ALL vectors regardless of index status. Corresponds to `MemoryReindexSchema`.' + security: + - ManagementSessionAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + force: + type: boolean + default: false + description: When true, marks all memories needs_reindex=1 before running. + additionalProperties: false + responses: + '200': + description: Reindex started + content: + application/json: + schema: + type: object + properties: + started: + type: boolean + pending: + type: integer + description: Memories still pending after this batch + '400': + description: Validation error + '401': + $ref: '#/components/responses/Unauthorized' + /api/settings/memory: + get: + tags: + - Memory + - Settings + summary: Get memory settings + description: Returns the extended memory settings including 7 new fields added in plan 21 (embeddingSource, embeddingProviderModel, transformersEnabled, staticEnabled, rerankEnabled, rerankProviderModel, vectorStore). + security: + - ManagementSessionAuth: [] + responses: + '200': + description: Extended memory settings + content: + application/json: + schema: + $ref: '#/components/schemas/MemorySettingsExtended' + '401': + $ref: '#/components/responses/Unauthorized' + put: + tags: + - Memory + - Settings + summary: Update memory settings + description: 'Update any subset of the extended memory settings. All fields are optional; only provided fields are updated. Schema: `MemorySettingsExtendedSchema`.' + security: + - ManagementSessionAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MemorySettingsExtended' + responses: + '200': + description: Updated memory settings + content: + application/json: + schema: + $ref: '#/components/schemas/MemorySettingsExtended' + '400': + description: Validation error + '401': + $ref: '#/components/responses/Unauthorized' + /api/settings/qdrant: + get: + tags: + - Memory + - Settings + summary: Get Qdrant settings + description: Returns current Qdrant configuration. The `apiKey` field is never returned raw — use `hasApiKey` / `apiKeyMasked` instead. + security: + - ManagementSessionAuth: [] + responses: + '200': + description: Qdrant settings + content: + application/json: + schema: + $ref: '#/components/schemas/QdrantSettings' + '401': + $ref: '#/components/responses/Unauthorized' + put: + tags: + - Memory + - Settings + summary: Update Qdrant settings + description: 'Update Qdrant configuration. Pass `apiKey: ""` to remove the stored key. Schema: `QdrantSettingsUpdateSchema`.' + security: + - ManagementSessionAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + host: + type: string + port: + type: integer + minimum: 1 + maximum: 65535 + collection: + type: string + minLength: 1 + embeddingModel: + type: string + minLength: 1 + apiKey: + type: string + description: Empty string removes the key + additionalProperties: false + responses: + '200': + description: Updated Qdrant settings + content: + application/json: + schema: + $ref: '#/components/schemas/QdrantSettings' + '400': + description: Validation error + '401': + $ref: '#/components/responses/Unauthorized' + /api/settings/qdrant/health: + get: + tags: + - Memory + summary: Qdrant health probe + description: Performs a liveness check against the configured Qdrant instance. Returns latency and any connection error (sanitized — no stack traces). + security: + - ManagementSessionAuth: [] + responses: + '200': + description: Health result + content: + application/json: + schema: + $ref: '#/components/schemas/QdrantHealthResult' + '401': + $ref: '#/components/responses/Unauthorized' + /api/settings/qdrant/search: + post: + tags: + - Memory + summary: Qdrant semantic search test + description: 'Performs a test semantic search against the Qdrant collection. Useful for validating that the integration works end-to-end. Schema: `QdrantSearchSchema`.' + security: + - ManagementSessionAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - query + properties: + query: + type: string + minLength: 1 + topK: + type: integer + minimum: 1 + maximum: 50 + default: 5 + additionalProperties: false + responses: + '200': + description: Search results + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + type: object + '400': + description: Validation error + '401': + $ref: '#/components/responses/Unauthorized' + '503': + description: Qdrant unavailable (structured error, no stack trace) + /api/settings/qdrant/cleanup: + post: + tags: + - Memory + summary: Clean up expired Qdrant points + description: Removes Qdrant points for memories that have expired or exceeded the configured retention window. + security: + - ManagementSessionAuth: [] + responses: + '200': + description: Cleanup result + content: + application/json: + schema: + type: object + properties: + deleted: + type: integer + checked: + type: integer + '401': + $ref: '#/components/responses/Unauthorized' + '503': + description: Qdrant unavailable (structured error, no stack trace) + /api/settings/qdrant/embedding-models: + get: + tags: + - Memory + summary: List Qdrant embedding models + description: Returns the list of embedding models available for use with Qdrant. + security: + - ManagementSessionAuth: [] + responses: + '200': + description: Embedding models list + content: + application/json: + schema: + type: object + properties: + models: + type: array + items: + type: string + '401': + $ref: '#/components/responses/Unauthorized' # ─── Proxy Endpoints ────────────────────────────────────────── /api/v1/chat/completions: @@ -4006,6 +4816,134 @@ components: $ref: "#/components/schemas/ValidationErrorResponse" schemas: + MemoryEntry: + type: object + description: A single persisted memory entry + properties: + id: + type: string + description: UUID + apiKeyId: + type: string + sessionId: + type: string + nullable: true + type: + type: string + enum: + - factual + - episodic + - procedural + - semantic + key: + type: string + description: Stable upsert key (e.g. preference:i_prefer_python) + content: + type: string + metadata: + type: object + additionalProperties: true + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + expiresAt: + type: string + format: date-time + nullable: true + needsReindex: + type: integer + description: 1 if the vector for this memory is stale or missing + MemorySettingsExtended: + type: object + description: Extended memory settings including 7 new fields from plan 21. All fields are optional for PUT (patch semantics). + properties: + enabled: + type: boolean + maxTokens: + type: integer + minimum: 0 + maximum: 16000 + retentionDays: + type: integer + minimum: 1 + maximum: 365 + strategy: + type: string + enum: + - recent + - semantic + - hybrid + skillsEnabled: + type: boolean + embeddingSource: + type: string + enum: + - remote + - static + - transformers + - auto + description: Which embedding source to use. "auto" = remote > static > transformers. + embeddingProviderModel: + type: string + nullable: true + description: Embedding provider/model in "provider/model" format (e.g. openai/text-embedding-3-small). + transformersEnabled: + type: boolean + description: Opt-in for Transformers.js local MiniLM model (~400MB RAM) + staticEnabled: + type: boolean + description: Opt-in for static potion-base-8M local model + rerankEnabled: + type: boolean + description: Enable reranking step (+200-500ms/req) + rerankProviderModel: + type: string + nullable: true + description: Rerank provider/model in "provider/model" format + vectorStore: + type: string + enum: + - sqlite-vec + - qdrant + - auto + description: Which vector backend to use + QdrantSettings: + type: object + description: Qdrant vector database configuration (read shape — no raw apiKey) + properties: + enabled: + type: boolean + host: + type: string + port: + type: integer + minimum: 1 + maximum: 65535 + collection: + type: string + embeddingModel: + type: string + hasApiKey: + type: boolean + apiKeyMasked: + type: string + nullable: true + description: First 4 chars of the configured API key, or null + QdrantHealthResult: + type: object + description: Result of a Qdrant liveness probe + properties: + ok: + type: boolean + latencyMs: + type: number + error: + type: string + nullable: true + description: Sanitized error message (no stack traces) AgentSkill: type: object description: >- diff --git a/next.config.mjs b/next.config.mjs index 10e9c1410d..7a4db27e4e 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -149,6 +149,7 @@ const nextConfig = { "koffi", "tough-cookie", "@ngrok/ngrok", + "@huggingface/transformers", "child_process", "fs", "path", diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 71dd2349e5..109930bf86 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -2290,9 +2290,84 @@ export async function handleChatCore({ }) ) { try { + // Plan 21 FAIL #1 fix: extract the last user message and pass it as + // `query`. Without this, `config.query` is undefined in retrieveMemories + // and the semantic/hybrid branches (sqlite-vec + RRF, and Qdrant + // tier-2) never fire from the chat hot path — they only fire in the + // Playground (retrievePreview, which gets `query` as a positional arg). + const lastUserQuery = ((): string => { + // Responses API item types that are NOT user input — never accept + // their text as the retrieval query (e.g. function_call_output is the + // tool's reply, reasoning is the model's chain of thought). + const NON_USER_TYPES = new Set([ + "function_call", + "function_call_output", + "tool_call", + "tool_call_output", + "reasoning", + "computer_call", + "computer_call_output", + "web_search_call", + "file_search_call", + ]); + + function pickFrom(arr: unknown[]): string { + for (let i = arr.length - 1; i >= 0; i--) { + const item = arr[i] as Record | undefined; + if (!item) continue; + // Chat API: only role==="user" items. Responses API items often + // have type instead of role — skip non-user types like + // function_call_output so the tool's reply doesn't leak into the + // memory query. + if (item.role !== undefined && item.role !== "user") continue; + if (item.role === undefined && typeof item.type === "string") { + if (NON_USER_TYPES.has(item.type)) continue; + } + const content = item.content ?? item.text; + if (typeof content === "string" && content.trim().length > 0) { + return content; + } + if (Array.isArray(content)) { + const parts: string[] = []; + for (const p of content) { + if (typeof p === "string") { + parts.push(p); + } else if (p && typeof p === "object") { + const pp = p as Record; + // Skip non-text content parts (image_url, tool_use, etc.) + const ptype = typeof pp.type === "string" ? pp.type : ""; + if ( + ptype && + ptype !== "text" && + ptype !== "input_text" && + ptype !== "output_text" + ) { + continue; + } + const t = pp.text ?? pp.input_text; + if (typeof t === "string") parts.push(t); + } + } + if (parts.length > 0) return parts.join(" ").trim(); + } + } + return ""; + } + const b = body as Record; + if (Array.isArray(b.messages)) { + const r = pickFrom(b.messages); + if (r) return r; + } + if (Array.isArray(b.input)) { + const r = pickFrom(b.input); + if (r) return r; + } + return ""; + })(); + const memories = await retrieveMemories( memoryOwnerId, - toMemoryRetrievalConfig(memorySettings) + toMemoryRetrievalConfig(memorySettings, { query: lastUserQuery }) ); if (memories.length > 0) { const injected = injectMemory( diff --git a/open-sse/mcp-server/tools/memoryTools.ts b/open-sse/mcp-server/tools/memoryTools.ts index 9ac7dd9ce2..b5ed467a95 100644 --- a/open-sse/mcp-server/tools/memoryTools.ts +++ b/open-sse/mcp-server/tools/memoryTools.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { retrieveMemories } from "@/lib/memory/retrieval"; import { createMemory, deleteMemory, listMemories } from "@/lib/memory/store"; import { MemoryType } from "@/lib/memory/types"; +import { getMemorySettings, toMemoryRetrievalConfig, DEFAULT_MEMORY_SETTINGS } from "@/lib/memory/settings"; export const MemorySearchSchema = z.object({ apiKeyId: z.string(), @@ -32,15 +33,17 @@ export const memoryTools = { description: "Search memories by query, type, or API key with token budget enforcement", inputSchema: MemorySearchSchema, handler: async (args: z.infer) => { - const config = { - enabled: true, - maxTokens: args.maxTokens || 2000, - retrievalStrategy: "exact" as const, - autoSummarize: false, - persistAcrossModels: false, - retentionDays: 30, - scope: "apiKey" as const, + // Plan 21 D16/Bug#7 fix: even on the error path the fallback must + // respect DEFAULT_MEMORY_SETTINGS.strategy instead of hardcoding "exact". + const memorySettings = + (await getMemorySettings().catch(() => null)) ?? DEFAULT_MEMORY_SETTINGS; + const baseConfig = toMemoryRetrievalConfig(memorySettings, { query: args.query, + }); + + const config = { + ...baseConfig, + maxTokens: args.maxTokens || (baseConfig.maxTokens ?? DEFAULT_MEMORY_SETTINGS.maxTokens), }; const memories = await retrieveMemories(args.apiKeyId, config); diff --git a/package-lock.json b/package-lock.json index 29a2f4d84c..e7ba5d616a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@huggingface/transformers": "^4.2.0", "@lobehub/icons": "^5.8.0", "@modelcontextprotocol/sdk": "^1.29.0", "@monaco-editor/react": "^4.7.0", @@ -71,6 +72,7 @@ "recharts": "^3.8.1", "selfsigned": "^5.5.0", "sql.js": "^1.14.1", + "sqlite-vec": "^0.1.9", "tsx": "^4.22.3", "undici": "^8.3.0", "update-notifier": "^7.3.1", @@ -2122,6 +2124,34 @@ "hono": "^4" } }, + "node_modules/@huggingface/jinja": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.5.9.tgz", + "integrity": "sha512-uWTG+l3VJRsl7EXxYizuL3P+cCPoc3cRqbWWRcQN0FhejRfbdq0RNhCmbY/YDtnTcz9icdLYuLDjsnz4d8JMuw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@huggingface/tokenizers": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@huggingface/tokenizers/-/tokenizers-0.1.3.tgz", + "integrity": "sha512-8rF/RRT10u+kn7YuUbUg0OF30K8rjTc78aHpxT+qJ1uWSqxT1MHi8+9ltwYfkFYJzT/oS+qw3JVfHtNMGAdqyA==", + "license": "Apache-2.0" + }, + "node_modules/@huggingface/transformers": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-4.2.0.tgz", + "integrity": "sha512-8BRCoBMH0XsWaEIamuR0LrJGAfftgHAfb2Vrffy0VKlSAE/MnUJ5/h/zTfEP3fDIft+nk7TqB8xXEyABGitBjQ==", + "license": "Apache-2.0", + "dependencies": { + "@huggingface/jinja": "^0.5.6", + "@huggingface/tokenizers": "^0.1.3", + "onnxruntime-node": "1.24.3", + "onnxruntime-web": "1.26.0-dev.20260416-b7804b056c", + "sharp": "^0.34.5" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2196,7 +2226,6 @@ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "license": "MIT", - "optional": true, "engines": { "node": ">=18" } @@ -3845,6 +3874,69 @@ "node": ">=12" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "license": "BSD-3-Clause" + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -6212,7 +6304,6 @@ "version": "25.9.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": ">=7.24.0 <7.24.7" @@ -7093,6 +7184,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/adm-zip": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz", + "integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==", + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, "node_modules/agent-base": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz", @@ -7752,6 +7852,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT" + }, "node_modules/bottleneck": { "version": "2.19.5", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", @@ -9610,7 +9717,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -9640,7 +9746,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", @@ -9708,6 +9813,12 @@ "node": ">=8" } }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -10069,6 +10180,12 @@ "benchmarks" ] }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "license": "MIT" + }, "node_modules/esast-util-from-estree": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", @@ -11177,6 +11294,12 @@ "node": ">=16" } }, + "node_modules/flatbuffers": { + "version": "25.9.23", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz", + "integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==", + "license": "Apache-2.0" + }, "node_modules/flatted": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", @@ -11814,6 +11937,35 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "license": "BSD-3-Clause", + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/global-directory": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", @@ -11855,7 +12007,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, "license": "MIT", "dependencies": { "define-properties": "^1.2.1", @@ -11923,6 +12074,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/guid-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", + "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==", + "license": "ISC" + }, "node_modules/hachure-fill": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", @@ -11955,7 +12112,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -13859,6 +14015,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -14506,6 +14668,12 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -14681,6 +14849,18 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -16342,7 +16522,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -16511,6 +16690,49 @@ "regex-recursion": "^6.0.2" } }, + "node_modules/onnxruntime-common": { + "version": "1.24.3", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.3.tgz", + "integrity": "sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==", + "license": "MIT" + }, + "node_modules/onnxruntime-node": { + "version": "1.24.3", + "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.24.3.tgz", + "integrity": "sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg==", + "hasInstallScript": true, + "license": "MIT", + "os": [ + "win32", + "darwin", + "linux" + ], + "dependencies": { + "adm-zip": "^0.5.16", + "global-agent": "^3.0.0", + "onnxruntime-common": "1.24.3" + } + }, + "node_modules/onnxruntime-web": { + "version": "1.26.0-dev.20260416-b7804b056c", + "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.26.0-dev.20260416-b7804b056c.tgz", + "integrity": "sha512-MD6Ss4GSpQBo6zqoJzyT9LRbKYs7x/JVN23FT24EcEvlqF4VuzPOeH6X38orZPKHQDbprn7K+SBpu0/mj2CQiw==", + "license": "MIT", + "dependencies": { + "flatbuffers": "^25.1.24", + "guid-typescript": "^1.0.9", + "long": "^5.2.3", + "onnxruntime-common": "1.24.0-dev.20251116-b39e144322", + "platform": "^1.3.6", + "protobufjs": "^7.2.4" + } + }, + "node_modules/onnxruntime-web/node_modules/onnxruntime-common": { + "version": "1.24.0-dev.20251116-b39e144322", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.0-dev.20251116-b39e144322.tgz", + "integrity": "sha512-BOoomdHYmNRL5r4iQ4bMvsl2t0/hzVQ3OM3PHD0gxeXu1PmggqBv3puZicEUVOA3AtHHYmqZtjMj9FOfGrATTw==", + "license": "MIT" + }, "node_modules/open": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", @@ -17016,6 +17238,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", + "license": "MIT" + }, "node_modules/playwright": { "version": "1.60.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", @@ -17235,6 +17463,30 @@ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "license": "ISC" }, + "node_modules/protobufjs": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.1.tgz", + "integrity": "sha512-4K0myLaWL5EteuSAro91EGFgcfVgxb64Jx+7oDAY6GOkXD4M69yuSEljNcInGVCA5sOPxmZ/EqDLj2x0Q0+Ygg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxifly": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/proxifly/-/proxifly-3.0.1.tgz", @@ -18126,6 +18378,29 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "license": "BSD-3-Clause", + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/roarr/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, "node_modules/robust-predicates": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", @@ -18424,6 +18699,12 @@ "semver": "bin/semver.js" } }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "license": "MIT" + }, "node_modules/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", @@ -18450,6 +18731,33 @@ "url": "https://opencollective.com/express" } }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/serve-static": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", @@ -18530,7 +18838,6 @@ "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "hasInstallScript": true, "license": "Apache-2.0", - "optional": true, "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", @@ -18574,7 +18881,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", - "optional": true, "bin": { "semver": "bin/semver.js" }, @@ -18897,6 +19203,84 @@ "integrity": "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A==", "license": "MIT" }, + "node_modules/sqlite-vec": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec/-/sqlite-vec-0.1.9.tgz", + "integrity": "sha512-L7XJWRIBNvR9O5+vh1FQ+IGkh/3D2AzVksW5gdtk28m78Hy8skFD0pqReKH1Yp0/BUKRGcffgKvyO/EON5JXpA==", + "license": "MIT OR Apache", + "optionalDependencies": { + "sqlite-vec-darwin-arm64": "0.1.9", + "sqlite-vec-darwin-x64": "0.1.9", + "sqlite-vec-linux-arm64": "0.1.9", + "sqlite-vec-linux-x64": "0.1.9", + "sqlite-vec-windows-x64": "0.1.9" + } + }, + "node_modules/sqlite-vec-darwin-arm64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-darwin-arm64/-/sqlite-vec-darwin-arm64-0.1.9.tgz", + "integrity": "sha512-jSsZpE42OfBkGL/ItyJTVCUwl6o6Ka3U5rc4j+UBDIQzC1ulSSKMEhQLthsOnF/MdAf1MuAkYhkdKmmcjaIZQg==", + "cpu": [ + "arm64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/sqlite-vec-darwin-x64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-darwin-x64/-/sqlite-vec-darwin-x64-0.1.9.tgz", + "integrity": "sha512-KDlVyqQT7pnOhU1ymB9gs7dMbSoVmKHitT+k1/xkjarcX8bBqPxWrGlK/R+C5WmWkfvWwyq5FfXfiBYCBs6PlA==", + "cpu": [ + "x64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/sqlite-vec-linux-arm64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-linux-arm64/-/sqlite-vec-linux-arm64-0.1.9.tgz", + "integrity": "sha512-5wXVJ9c9kR4CHm/wVqXb/R+XUHTdpZ4nWbPHlS+gc9qQFVHs92Km4bPnCKX4rtcPMzvNis+SIzMJR1SCEwpuUw==", + "cpu": [ + "arm64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/sqlite-vec-linux-x64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-linux-x64/-/sqlite-vec-linux-x64-0.1.9.tgz", + "integrity": "sha512-w3tCH8xK2finW8fQJ/m8uqKodXUZ9KAuAar2UIhz4BHILfpE0WM/MTGCRfa7RjYbrYim5Luk3guvMOGI7T7JQA==", + "cpu": [ + "x64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/sqlite-vec-windows-x64": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/sqlite-vec-windows-x64/-/sqlite-vec-windows-x64-0.1.9.tgz", + "integrity": "sha512-y3gEIyy/17bq2QFPQOWLE68TYWcRZkBQVA2XLrTPHNTOp55xJi/BBBmOm40tVMDMjtP+Elpk6UBUXdaq+46b0Q==", + "cpu": [ + "x64" + ], + "license": "MIT OR Apache", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -20016,7 +20400,6 @@ "version": "7.24.6", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "dev": true, "license": "MIT" }, "node_modules/unicode-emoji-modifier-base": { diff --git a/package.json b/package.json index 33535743c7..5ed04610bc 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,7 @@ "test:e2e": "node scripts/dev/run-playwright-tests.mjs test tests/e2e/*.spec.ts", "test:protocols:e2e": "node scripts/dev/run-protocol-clients-tests.mjs", "test:vitest": "vitest run --config vitest.mcp.config.ts", + "test:vitest:ui": "vitest run --config vitest.config.ts tests/unit/ui", "test:ecosystem": "node scripts/dev/run-ecosystem-tests.mjs", "test:system": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx --import ./open-sse/utils/setupPolyfill.ts --test --test-force-exit --test-concurrency=1 tests/e2e/system-failover.test.ts", "test:coverage": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true c8 --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 40 --lines 40 --functions 40 --branches 40 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts", @@ -126,7 +127,7 @@ "coverage:summary": "node scripts/check/test-report-summary.mjs --input coverage/coverage-summary.json --output coverage/coverage-report.md", "check:pr-test-policy": "node scripts/check/check-pr-test-policy.mjs", "coverage:report:legacy": "c8 report --output-dir coverage --exclude=open-sse --reporter=text --reporter=text-summary", - "test:all": "npm run test:unit && npm run test:vitest && npm run test:ecosystem && npm run test:e2e", + "test:all": "npm run test:unit && npm run test:vitest && npm run test:vitest:ui && npm run test:ecosystem && npm run test:e2e", "check": "npm run lint && npm run test", "prepublishOnly": "npm run build:cli-api && npm run build:cli && npm run check:pack-artifact", "postinstall": "node scripts/build/postinstall.mjs", @@ -141,6 +142,7 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@huggingface/transformers": "^4.2.0", "@lobehub/icons": "^5.8.0", "@modelcontextprotocol/sdk": "^1.29.0", "@monaco-editor/react": "^4.7.0", @@ -195,6 +197,7 @@ "recharts": "^3.8.1", "selfsigned": "^5.5.0", "sql.js": "^1.14.1", + "sqlite-vec": "^0.1.9", "tsx": "^4.22.3", "undici": "^8.3.0", "update-notifier": "^7.3.1", diff --git a/src/app/(dashboard)/dashboard/memory/components/EditMemoryModal.tsx b/src/app/(dashboard)/dashboard/memory/components/EditMemoryModal.tsx new file mode 100644 index 0000000000..e6111e8b44 --- /dev/null +++ b/src/app/(dashboard)/dashboard/memory/components/EditMemoryModal.tsx @@ -0,0 +1,161 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Modal, Button, Input, Select } from "@/shared/components"; +import { useTranslations } from "next-intl"; + +interface Memory { + id: string; + type: "factual" | "episodic" | "procedural" | "semantic"; + key: string; + content: string; + metadata: Record; +} + +interface Props { + memory: Memory | null; + isOpen: boolean; + onClose: () => void; + onSaved: () => void; +} + +export default function EditMemoryModal({ memory, isOpen, onClose, onSaved }: Props) { + const t = useTranslations("memory"); + const [type, setType] = useState<"factual" | "episodic" | "procedural" | "semantic">("factual"); + const [key, setKey] = useState(""); + const [content, setContent] = useState(""); + const [metadataStr, setMetadataStr] = useState("{}"); + const [metadataError, setMetadataError] = useState(""); + const [isSaving, setIsSaving] = useState(false); + const [error, setError] = useState(""); + + useEffect(() => { + if (memory && isOpen) { + setType(memory.type); + setKey(memory.key); + setContent(memory.content); + setMetadataStr(JSON.stringify(memory.metadata ?? {}, null, 2)); + setMetadataError(""); + setError(""); + } + }, [memory, isOpen]); + + const handleMetadataChange = (value: string) => { + setMetadataStr(value); + try { + JSON.parse(value); + setMetadataError(""); + } catch { + setMetadataError(t("editModal.metadataInvalid")); + } + }; + + const handleSave = async () => { + if (!memory) return; + if (metadataError) return; + setIsSaving(true); + setError(""); + try { + let metadata: Record = {}; + try { + metadata = JSON.parse(metadataStr); + } catch { + setError(t("editModal.metadataInvalid")); + return; + } + const res = await fetch(`/api/memory/${memory.id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ type, key, content, metadata }), + }); + if (res.ok) { + onSaved(); + onClose(); + } else { + const data = await res.json().catch(() => null); + setError(data?.error?.message ?? t("editModal.saveFailed")); + } + } catch { + setError(t("editModal.saveFailed")); + } finally { + setIsSaving(false); + } + }; + + return ( + + + + + } + > +
+ {error && ( +
+ {error} +
+ )} +
+ + +
+
+ + setKey(e.target.value)} + placeholder={t("keyPlaceholder")} + className="w-full" + /> +
+
+ +