Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
**
!openapi.json
!server/
!server/live/
!server/live/**
!deploy/
!deploy/dashboard-bff/
!deploy/dashboard-bff/Dockerfile
!deploy/dashboard-bff/package.json
!deploy/dashboard-bff/package-lock.json
server/live/**/*.test.ts
server/live/test-support.ts
**/.env*
21 changes: 21 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,24 @@ MOCK_DB=.data/mock-db.json
# 指向已部署的后端就能直接看真实数据,不用再起 mock:
# MOCK_ORIGIN=http://<后端地址>
MOCK_ORIGIN=

# 可选真实BFF:复制本文件为.env.live,填写实际环境,然后运行 pnpm live。
# 前端另用.env.local设置 VITE_API_BASE=http://127.0.0.1:5281,启动/构建前生效。
MEMOS_API_BASE_URL=http://127.0.0.1:8000
MEMOS_SERVICE_URL=http://127.0.0.1:8003
DASHBOARD_HOST=127.0.0.1
DASHBOARD_PORT=5281
DASHBOARD_ORIGINS=http://127.0.0.1:5274,http://localhost:5274,http://127.0.0.1:5275,http://localhost:5275
DASHBOARD_DATA_DIR=.data/live
MEMOS_TIMEOUT_MS=10000
# 必须是已注册、激活且有创建cube权限的上游普通用户;不凭ID存在假设有权限。
MEMOS_CUBE_OWNER=
# 初始映射仅首次启动写入projects.json。请使用真实、已授权的user/cube/tenant。
DASHBOARD_WORKSPACES=[]
# 原生网关calls-*.jsonl所在目录;不可读取时日志/用量报错,不伪造为零。
MEMORY_CALL_LOG_DIR=
# 可选:与网关共享的gateway.env路径。共享整个父目录,BFF需要写权限。
DASHBOARD_GATEWAY_ENV=
# 服务端私有扩展token:自行生成至少32字符,BFF与MemOS配置一致,禁止VITE_前缀。
DASHBOARD_MEMORY_TOKEN=
DASHBOARD_PROMPT_TOKEN=
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Keep checked-out text identical to Git blobs, including the immutable frontend.
* text=auto eol=lf
27 changes: 27 additions & 0 deletions .github/workflows/dashboard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Dashboard checks
on:
push:
branches: [dashboard]
pull_request:
branches: [dashboard]
permissions:
contents: read
concurrency:
group: dashboard-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
node:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- uses: pnpm/action-setup@v4
with:
version: 12.14.0
- run: pnpm install --frozen-lockfile
- run: pnpm verify
- run: node scripts/issue-acceptance/verify-frontend-unchanged.ts
- run: node --test server/live/*.test.ts scripts/mock-api.test.ts scripts/acceptance.test.ts scripts/issue-acceptance/isolated/*.test.ts scripts/issue-acceptance/contract/*.test.ts scripts/issue-acceptance/real/*.test.ts
- run: npx --yes @redocly/cli@2.67.1 lint openapi.json
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,23 @@ dist
*.local
*.log
.data
.letta/
__pycache__/
*.pyc

.env
.env.*
!.env.example

# 开发期用来跟线上页面做像素/结构比对的脚本与素材,不进仓库
scripts/*
!scripts/mock-api.test.ts
!scripts/acceptance.test.ts
!scripts/test-support.ts
!scripts/tool-types.ts
!scripts/issue-acceptance/
!scripts/issue-acceptance/**
reference/

# 内部对接笔记,含内网地址与资源名,不进公开仓库
docs/BACKEND-INTEGRATION.md
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ node_modules
dist
reference
.data
.letta
pnpm-lock.yaml

# 样式表是编译产物,按原样保留
Expand Down
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,22 @@ MemOS 记忆服务的 Web 控制台:接入说明、接口密钥、调用日志
模型清单、自定义抽取、用量监控。

前端是 Vite + React 19;仓库自带一个独立的 mock 后端(`server/mock/`),只用于本地开发。
真实后端需要实现的接口见 `openapi.json`(OpenAPI 3.1,37 条路径)。
原有接口定义见 `openapi.json`(OpenAPI 3.1,37 条路径);live 模型接口的补充契约见
`server/live/models.openapi.json`。真实接入方式见下节。

## 连接真实MemOS服务

本分支新增独立的 `server/live/` BFF,与 `server/mock/` 并存。前端保持上游
`481a291` 的原始内容,通过 `VITE_API_BASE` 连接BFF;并非在原生MemOS中新增全部Dashboard路由。

- [本地启动、配置与安全边界](docs/LIVE-BACKEND.md)
- [架构、数据映射及兼容性](docs/ARCHITECTURE.md)
- [Docker部署与必要上游扩展](deploy/dashboard-bff/README.md)
- [测试命令与验收边界](docs/testing.md)
- [新增依赖与许可证范围](docs/DEPENDENCIES.md)

按ID软删除、工作区Prompt和网关可信审计/密钥热加载需要明确安装相应扩展。
它们不是“上游零改动”,也不替代原生MCP已有的删除语义。

## 快速开始

Expand All @@ -13,7 +28,7 @@ pnpm install
pnpm dev:all # 同时起 mock 后端(5280) 与前端(5274)
```

打开 <http://localhost:5274/cn/apikeys>。需要 Node 20+ 与 pnpm。
打开 <http://localhost:5274/cn/apikeys>。需要 Node 22.18+ 与 pnpm(原生执行TS)。

其他命令:

Expand All @@ -36,7 +51,7 @@ pnpm verify # typecheck + lint + format:check + build
| 调用日志 | `/cn/requests` | 分页 + 关键词搜索 + 事件类型筛选,点行看请求/响应详情 |
| 记忆列表 | `/cn/memoryList` | 6 个记忆类型标签 + 分页 + 搜索 + 批量删除 |
| 记忆图谱 | `/cn/memoryGraph` | 实体与关系的力导向图,支持缩放、拖拽与节点详情 |
| 模型列表 | `/cn/models` | 后端返回的模型清单,支持搜索与复制 |
| 模型列表 | `/cn/models` | MemOS 运行实例的模型清单,支持页签切换与复制 |
| 自定义抽取 | `/cn/customExtraction` | Prompt 配置与快速调试两个标签 |
| 用量监控 | `/cn/usage` | 按天聚合的调用量与 token,图表 + 明细 |
| 接口文档 | `/cn/docs` | 服务地址、MCP 接入配置 |
Expand Down Expand Up @@ -82,7 +97,8 @@ openapi.json 接口定义

## 接口定义

接口的权威定义是仓库根目录的 `openapi.json`(OpenAPI 3.1,37 条路径);前端侧的路径、
原有 37 条接口的定义是仓库根目录的 `openapi.json`(OpenAPI 3.1);新增 live 模型接口见
`server/live/models.openapi.json`,需一起升级 BFF 和原生 memory 扩展。前端侧的路径、
请求体与返回类型在 `src/lib/api.ts`。

几条容易踩的约定:
Expand Down
9 changes: 9 additions & 0 deletions deploy/dashboard-bff/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM node:22-bookworm-slim@sha256:c3de60bf2f9dd0ac6370e6117950ff62d6e339527e7472301c9c78a017978392
WORKDIR /app
COPY deploy/dashboard-bff/package*.json ./
RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund --fetch-timeout=20000 --fetch-retries=1
COPY openapi.json ./
COPY server/live ./server/live
ENV NODE_ENV=production DASHBOARD_HOST=0.0.0.0 DASHBOARD_PORT=5281
EXPOSE 5281
CMD ["node", "server/live/server.ts"]
106 changes: 106 additions & 0 deletions deploy/dashboard-bff/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Deploying the live Dashboard backend

Use the MemOS service and MCP gateway from this repository's `deployment` branch. The extensions
were developed against its `5ab3443d31303c4ca202759c69a72ff59f480ed4` layout (`/app/src/memos`
and `/gateway`). Review compatibility before updating the base: patch scripts check source anchors
and must fail rather than silently patch unknown code. Model and embedding configuration must work.

This directory contains runtime modules, image overlays and opt-in verification tools. Workstation
SSH relays, model-account repairs and one-off machine deployment scripts are not part of the feature.

## BFF image and state

The pinned Node 22 image supports native TypeScript execution (22.18+ required).
The entrypoint is `server/live/server.ts`; no transpiler/loader or new runtime dependency
is installed. Run `pnpm typecheck`/`pnpm verify` before building: Node strips types but does
not check them. Shared-contract imports are type-only and erased before module resolution.

Build from the repository root:

```bash
docker build -t memos-dashboard-bff:local -f deploy/dashboard-bff/Dockerfile .
```

Configure a private Compose environment file (never commit real values):

```dotenv
MEMOS_DOCKER_NETWORK=your-existing-memos-network
MEMOS_API_BASE_URL=http://memos:8000
MEMOS_SERVICE_URL=http://127.0.0.1:8003
MEMOS_CUBE_OWNER=your-existing-owner
DASHBOARD_WORKSPACES=[]
DASHBOARD_AUDIT_DIR=/absolute/path/to/gateway-audit
DASHBOARD_STATE_DIR=/absolute/path/to/dashboard-state
DASHBOARD_PUBLISHED_PORT=5281
```

Create and permission the host directories first. Add private extension tokens as described below,
then run `docker compose --env-file /path/to/private.env -f deploy/dashboard-bff/compose.yml up -d --build`.
The example uses an existing network, read-only root filesystem, dropped capabilities, read-only audit
mount and loopback-only published port. Its health check also verifies native MemOS health. Preserve
the state volume and use one BFF writer per state directory.

**There is no BFF administrator authentication.** Use authenticated private access or an SSH forward,
not a public `0.0.0.0` binding. CORS and workspace mappings do not provide administrator authentication.

## Required selected-ID soft deletion

The original product delete endpoint can physically delete nodes. The BFF therefore requires the
private extension for deletion and never falls back to that endpoint:

```bash
docker build --build-arg MEMOS_BASE_IMAGE=your-memos-base:tag \
-t memos-dashboard:soft-delete deploy/dashboard-bff/memory-extension
```

Use the resulting image for the existing MemOS service, preserving its environment, mounts and database
connections. Set `DASHBOARD_MEMORY_ENABLED=1` and a generated server-only `DASHBOARD_MEMORY_TOKEN`
of at least 32 characters on MemOS, and the same token on the BFF. See [retention semantics](memory-extension/README.md).

## Optional scoped Prompt support

To install both extensions, build the Prompt layer first and soft-delete on top:

```bash
docker build --build-arg MEMOS_BASE_IMAGE=your-memos-base:tag \
-t memos-dashboard:prompts deploy/dashboard-bff/prompt-extension
docker build --build-arg MEMOS_BASE_IMAGE=memos-dashboard:prompts \
-t memos-dashboard:extended deploy/dashboard-bff/memory-extension
```

MemOS needs `DASHBOARD_PROMPT_ENABLED=1`, a persistent private `DASHBOARD_PROMPT_DIR`, and a
`DASHBOARD_PROMPT_TOKEN` of at least 32 characters. Set the same token on the BFF. Configuration is
keyed by user/cube. See [supported scope](../../docs/PROMPTS.md). Native preference production is
configured separately; this extension does not automatically enable or relabel it.

## Gateway keys and trusted audit attribution

Mount `gateway-keys/` read-only at `/dashboard-keys` in the existing gateway image and use
`python /dashboard-keys/entrypoint.py` as its command. This composes the original tools, identity
resolver and audit sink with a file-backed verifier and trusted audit decorator. Preserve the
gateway's existing backend/model configuration and audit volume.

Mount a private directory containing `gateway.env` into **both** gateway and BFF: read-only on
the gateway, read-write on the BFF. Set `DASHBOARD_GATEWAY_ENV` on both to the file's container
path. Add these mounts through a local Compose override; the basic Compose example does not enable them.

The file contains a valid native `MEMOS_TENANTS` mapping: strong token keys map to a user string
or an object with `user_id` and `cube_id`. Use restrictive host permissions; never commit tokens.
Mount the **directory**, not the individual file, because updates use atomic rename. An empty mapping
keeps authentication enabled and denies every token. Missing/corrupt configuration fails closed.

New tool calls receive authenticated `cube_id` and public `api_key_id`. Legacy logs are not rewritten
or guessed from current mappings. This decorator is required for correct project/key statistics,
independently of whether key-creation UI is used.

## Verification and upgrades

Start with health and read-only REST/audit comparisons. Real mutating tests require dedicated fixtures
and explicit consent; see [testing](../../docs/testing.md). Key and preference verification tools require
`DASHBOARD_TEST_BFF` set to an origin reachable from their gateway container. They may create and clean
their own fixtures; read each tool's stated scope first.

Keep the previous image and protected state backup. Test extension guards against the exact native
image; do not change deletion semantics, disable authentication/TLS or restart unrelated databases
to make a test pass. Rollout/rollback orchestration is an operator responsibility, not a hidden
side effect of this portable Compose example.
23 changes: 23 additions & 0 deletions deploy/dashboard-bff/bootstrap-owner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Explicit provisioning inside MemOS container, reusing its persisted UserManager.

Does not mutate demo identity, grant admin role, or start/reconfigure a service.
"""
import json
import sys

from memos.mem_user.user_manager import UserManager, UserRole

if "--create-owner" not in sys.argv:
raise SystemExit("Explicit --create-owner is required")

manager = UserManager()
owner_id, owner_name = "dashboard-bff-owner", "Dashboard BFF Owner"
user = manager.get_user(owner_id)
if user is None:
created = manager.create_user(owner_name, role=UserRole.USER, user_id=owner_id)
if created != owner_id:
raise SystemExit("Owner name already belongs to another ID; refused")
user = manager.get_user(owner_id)
if user.user_name != owner_name or user.role != UserRole.USER or not user.is_active:
raise SystemExit("Existing owner differs from expected ordinary active user; refused")
print(json.dumps({"status": "PASS", "owner_id": owner_id, "role": user.role.value}))
46 changes: 46 additions & 0 deletions deploy/dashboard-bff/compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: memos-dashboard-bff
services:
bff:
build:
context: ../..
dockerfile: deploy/dashboard-bff/Dockerfile
image: memos-dashboard-bff:local
read_only: true
security_opt:
- no-new-privileges:true
cap_drop: [ALL]
tmpfs:
- /tmp:rw,noexec,nosuid,size=32m
environment:
MEMOS_API_BASE_URL: ${MEMOS_API_BASE_URL:-http://memos:8000}
MEMOS_SERVICE_URL: ${MEMOS_SERVICE_URL:-http://127.0.0.1:8003}
MEMOS_CUBE_OWNER: ${MEMOS_CUBE_OWNER:-}
DASHBOARD_PROMPT_TOKEN: ${DASHBOARD_PROMPT_TOKEN:-}
DASHBOARD_MEMORY_TOKEN: ${DASHBOARD_MEMORY_TOKEN:-}
DASHBOARD_GATEWAY_ENV: ${DASHBOARD_GATEWAY_ENV:-}
DASHBOARD_DATA_DIR: /data
MEMORY_CALL_LOG_DIR: /audit
DASHBOARD_ORIGINS: ${DASHBOARD_ORIGINS:-http://127.0.0.1:5274,http://localhost:5274,http://127.0.0.1:5275,http://localhost:5275}
DASHBOARD_WORKSPACES: ${DASHBOARD_WORKSPACES:?Set explicit project/user/cube/tenant mapping}
volumes:
- ${DASHBOARD_AUDIT_DIR:?Set existing audit directory}:/audit:ro
- ${DASHBOARD_STATE_DIR:?Set isolated BFF state directory}:/data
ports:
- 127.0.0.1:${DASHBOARD_PUBLISHED_PORT:-5281}:5281
networks: [memos]
restart: unless-stopped
healthcheck:
test:
[
CMD,
node,
-e,
"fetch('http://127.0.0.1:5281/api/meta').then(async r=>{const j=await r.json();if(!r.ok||j.data?.health?.status!=='healthy')process.exit(1)}).catch(()=>process.exit(1))",
]
interval: 30s
timeout: 5s
retries: 3
networks:
memos:
external: true
name: ${MEMOS_DOCKER_NETWORK:?Set the existing MemOS Docker network}
Loading
Loading