diff --git a/.github/workflows/_build-docs.yml b/.github/workflows/_build-docs.yml index 8bc9691d..338c0ec6 100644 --- a/.github/workflows/_build-docs.yml +++ b/.github/workflows/_build-docs.yml @@ -28,6 +28,7 @@ jobs: steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 with: + fetch-depth: 0 persist-credentials: false - name: Set up Node diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index d506b005..490920db 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -11,11 +11,17 @@ on: - 'README_ZH.md' - 'docs/**' - 'github-pages/**' + - 'reme/config/default.yaml' + - 'integrations/claude_code/README.md' + - 'integrations/hermes_agent/README.md' - 'reme_studio/README*.md' - 'reme_studio/public/og.jpg' - 'typescript/README*.md' + - 'typescript/docs/**' + - 'typescript/figures/**' - 'plugins/*/README*.md' - 'benchmark/*/README*.md' + - 'benchmark/toolmemory/gitcha.png' pull_request: branches: [main, master, dev, develop] paths: @@ -26,11 +32,17 @@ on: - 'README_ZH.md' - 'docs/**' - 'github-pages/**' + - 'reme/config/default.yaml' + - 'integrations/claude_code/README.md' + - 'integrations/hermes_agent/README.md' - 'reme_studio/README*.md' - 'reme_studio/public/og.jpg' - 'typescript/README*.md' + - 'typescript/docs/**' + - 'typescript/figures/**' - 'plugins/*/README*.md' - 'benchmark/*/README*.md' + - 'benchmark/toolmemory/gitcha.png' workflow_dispatch: concurrency: diff --git a/.github/workflows/ci-python-quality.yml b/.github/workflows/ci-python-quality.yml index 21cd7a87..d39302f6 100644 --- a/.github/workflows/ci-python-quality.yml +++ b/.github/workflows/ci-python-quality.yml @@ -34,7 +34,7 @@ jobs: - name: Install run: | pip install -q -e reme_studio -e ".[dev,core]" - pip install -q --no-deps -e plugins/auto-fin -e plugins/daily_paper + pip install -q --no-deps -e plugins/auto-fin -e plugins/daily_paper -e plugins/lme -e plugins/beam - name: Pre-commit starts run: pre-commit run --all-files diff --git a/.github/workflows/ci-python-tests.yml b/.github/workflows/ci-python-tests.yml index 00b3bbbf..9aab1e75 100644 --- a/.github/workflows/ci-python-tests.yml +++ b/.github/workflows/ci-python-tests.yml @@ -40,11 +40,12 @@ jobs: pip install -e reme_studio -e ".[dev,core]" pip install --no-deps -e plugins/auto-fin pip install -e plugins/daily_paper + pip install -e plugins/lme -e plugins/beam pip install coverage - name: Run unit tests run: | - coverage run -m pytest tests/unit plugins/auto-fin plugins/daily_paper \ + coverage run -m pytest tests/unit plugins/auto-fin plugins/daily_paper plugins/lme plugins/beam \ -v \ --tb=long \ -s \ diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index fe81de3d..1d812b79 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -6,13 +6,19 @@ on: paths: - "github-pages/**" - "docs/**" + - "reme/config/default.yaml" + - "integrations/claude_code/README.md" + - "integrations/hermes_agent/README.md" - "README.md" - "README_ZH.md" - "reme_studio/README*.md" - "reme_studio/public/og.jpg" - "typescript/README*.md" + - "typescript/docs/**" + - "typescript/figures/**" - "plugins/*/README*.md" - "benchmark/*/README*.md" + - "benchmark/toolmemory/gitcha.png" - "AGENTS.md" - ".github/workflows/deploy-docs.yml" - ".github/workflows/_build-docs.yml" diff --git a/.github/workflows/release-typescript.yml b/.github/workflows/release-typescript.yml index b07a2994..4d6fd06e 100644 --- a/.github/workflows/release-typescript.yml +++ b/.github/workflows/release-typescript.yml @@ -2,8 +2,9 @@ # 1. Update typescript/package.json and package-lock.json to the release version and merge them. # 2. Configure npm Trusted Publishing for agentscope-ai/ReMe and this workflow file. # 3. Run this workflow manually with the exact package version (an optional v prefix is accepted). -# 4. Configure ClawHub Trusted Publishing for agentscope-ai/ReMe and this workflow file. -# 5. Use the `next` tag for prereleases and `latest` only for stable releases. +# 4. Keep the ClawHub publication docs in typescript/docs/openclaw.md and openclaw.zh-CN.md. +# 5. Configure ClawHub Trusted Publishing for agentscope-ai/ReMe and this workflow file. +# 6. Use the `next` tag for prereleases and `latest` only for stable releases. name: Release / TypeScript integrations @@ -112,6 +113,8 @@ jobs: - name: Pack ClawHub tarball working-directory: typescript run: | + test -f docs/openclaw.md + test -f docs/openclaw.zh-CN.md mkdir -p "${RUNNER_TEMP}/reme-clawhub-package" npm run pack:clawhub -- "${RUNNER_TEMP}/reme-clawhub-package" @@ -171,7 +174,7 @@ jobs: tags: ${{ inputs.npm_tag }} source_repo: ${{ github.repository }} source_commit: ${{ github.sha }} - source_ref: ${{ github.ref }} + source_ref: ${{ github.sha }} source_path: typescript package_artifact_name: agentscope-ai-reme-clawhub-${{ inputs.version }} dry_run: false diff --git a/AGENTS.md b/AGENTS.md index 169e2b4f..ba8ae6ac 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,8 +46,8 @@ and concise documentation together. - `reme/components/service/`: local CLI, HTTP, and MCP service backends. - `reme/components/`: agent wrappers, model adapters, stores, catalogs, graphs, indexes, clients, tokenizers, and outbound proxies. -- `reme/steps/`: registered job steps grouped by common, file I/O, index, evolve, cookbook, benchmark, and transfer - concerns. +- `reme/steps/`: registered job steps grouped by common, file I/O, index, evolve, cookbook, and transfer + concerns, plus shared benchmark base classes under `benchmark/`. - `reme/utils/`: shared utilities, including service discovery, logging, web-static resolution, session I/O, token accounting, and wikilink handling. - `tests/unit/`: primary fast, isolated validation suite. @@ -56,12 +56,14 @@ and concise documentation together. `@agentscope-ai/reme_studio` npm static distribution. - `typescript/`: the independently published `@agentscope-ai/reme` package, including the shared TypeScript client and DeepSeek Harness and OpenClaw adapters. -- `plugins/`: installable ReMe extensions, such as Auto Fin. +- `plugins/`: installable ReMe extensions, including Auto Fin and LME/BEAM benchmark Steps and application presets. - `integrations/`: adapters that connect ReMe to external agent hosts, such as Claude Code, DSH, and Hermes Agent. - `skills/`: standalone skills; `reme_memory` calls ReMe, while other skills may use separate tools or direct-file conventions. - `benchmark/` and `cookbook/`: runnable evaluations and example workflows. - `docs/`: README-linked supporting pages and figures. +- `github-pages/`: VitePress build shell, generated-content assembly, documentation checks, and GitHub Pages output. The + canonical theme and guides remain under `docs/`; `.generated/` and `dist/` are disposable. ## Development Setup @@ -195,6 +197,9 @@ Integration tests may contact real model providers, services, or agent subproces run credentialed or externally mutating tests automatically; run them only when the task requires them and the necessary environment has been supplied or authorized. Mock network, model, and subprocess boundaries in unit tests. +If documentation or the documentation theme changes, run `npm test` and `npm run build` from `github-pages/`. The Job +reference is generated from `reme/config/default.yaml`; do not edit generated pages directly. + ## Change Guardrails - Preserve unrelated user changes in a dirty working tree. diff --git a/README.md b/README.md index 23593b9f..55485d34 100644 --- a/README.md +++ b/README.md @@ -51,12 +51,12 @@ users retain control of the durable files. - [2026.08] - Published [`@agentscope-ai/reme`](https://www.npmjs.com/package/@agentscope-ai/reme), providing native ReMe memory integrations for DeepSeek Harness and OpenClaw plus a shared TypeScript HTTP client. -- [2026.08] - Published the [ReMe blog](https://agentscope-ai.github.io/ReMe/?doc=en-reme-blog), an end-to-end introduction to its local-first memory +- [2026.08] - Published the [ReMe blog](https://reme.agentscope.io/en/reme-blog), an end-to-end introduction to its local-first memory architecture, self-evolving workflows, hybrid search, proactive discovery, and benchmark results. -- [2026.08] - [Experience-driven enhancement method](https://reme.agentscope.io/?doc=toolmemory-en) of agent tool-use execution built +- [2026.08] - [Experience-driven enhancement method](https://reme.agentscope.io/en/benchmarks/toolmemory) of agent tool-use execution built on ReMe is available on [arXiv:2608.03403](https://arxiv.org/abs/2608.03403). -- [2026.07] - Introduced optional plugins: [Daily Paper](https://reme.agentscope.io/?doc=daily-paper-en) for paper discovery and - analysis, and [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-en) for researching the latest 24 hours of topic-related CLS news +- [2026.07] - Introduced optional plugins: [Daily Paper](https://reme.agentscope.io/en/plugins/daily-paper) for paper discovery and + analysis, and [Auto Fin](https://reme.agentscope.io/en/plugins/auto-fin) for researching the latest 24 hours of topic-related CLS news with local-memory search and validated historical wikilinks. - [2026.07] - Our paper [Remember Me, Refine Me: A Dynamic Procedural Memory Framework for Experience-Driven Agent Evolution](https://aclanthology.org/2026.findings-acl.829/) @@ -171,7 +171,7 @@ Related: [[digest/wiki/memory-as-file.md]] The `core` installation includes Studio. After starting ReMe, open to browse, edit, and search the workspace. To add Studio to a base installation, use `pip install "reme-ai[web]"`. See the -[ReMe Studio guide](https://reme.agentscope.io/?doc=studio-en) for source builds, configuration, and development. +[ReMe Studio guide](https://reme.agentscope.io/en/workspace/studio) for source builds, configuration, and development. ## 🤝 Use ReMe with Your Agent @@ -308,12 +308,12 @@ benchmark. | Benchmark | Setting | Sample size | Agentic score | Focus | | --------------------------------------------------------------------------- | ------------ | -----------------------: | ------------: | ------------------------------------------------------------------ | -| **[LongMemEval cleaned-s](https://reme.agentscope.io/?doc=longmemeval-en)** | **Overall** | **500 questions** | **89.4%** | Cross-session retrieval, knowledge updates, and temporal reasoning | -| [BEAM](https://reme.agentscope.io/?doc=beam-en) | 100K context | 20 cases / 400 questions | 66.1% | Ten types of long-context memory tasks | -| [BEAM](https://reme.agentscope.io/?doc=beam-en) | 1M context | 35 cases / 700 questions | 65.0% | Ultra-long conversation settings | +| **[LongMemEval cleaned-s](https://reme.agentscope.io/en/benchmarks/longmemeval)** | **Overall** | **500 questions** | **89.4%** | Cross-session retrieval, knowledge updates, and temporal reasoning | +| [BEAM](https://reme.agentscope.io/en/benchmarks/beam) | 100K context | 20 cases / 400 questions | 66.1% | Ten types of long-context memory tasks | +| [BEAM](https://reme.agentscope.io/en/benchmarks/beam) | 1M context | 35 cases / 700 questions | 65.0% | Ultra-long conversation settings | ReMe also achieved a **0.580 PROC score across five user personas** in the repository's -[π-Bench evaluation](https://reme.agentscope.io/?doc=pibench-en), 2.4% above NanoBot under the same test-model configuration. PROC +[π-Bench evaluation](https://reme.agentscope.io/en/benchmarks/pibench), 2.4% above NanoBot under the same test-model configuration. PROC measures proactive handling of hidden intent, clarification, cross-session preferences and conventions, task dependencies, and underspecified requests. @@ -326,8 +326,8 @@ plugins; see the source distributions and their documentation for [Daily Paper]( | Plugin | Capability | | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -| [Daily Paper](https://reme.agentscope.io/?doc=daily-paper-en) | Discover and rank papers, analyze PDFs with an agent, and generate file-native notes and a five-minute brief. | -| [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-en) | Fetch topic-related CLS news, search ReMe history, and generate wikilink-backed Markdown reports. | +| [Daily Paper](https://reme.agentscope.io/en/plugins/daily-paper) | Discover and rank papers, analyze PDFs with an agent, and generate file-native notes and a five-minute brief. | +| [Auto Fin](https://reme.agentscope.io/en/plugins/auto-fin) | Fetch topic-related CLS news, search ReMe history, and generate wikilink-backed Markdown reports. | See [Plugin Management](docs/en/plugin_management.md) to install, inspect, validate, enable, and uninstall ReMe plugins. @@ -338,6 +338,8 @@ These guides cover the main user workflows and the runtime contracts implemented | Guide | What you will learn | | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | [Quick Start](docs/en/quick_start.md) | Install ReMe, start the service, and run the first file and memory operations. | +| [Configuration](docs/en/configuration.md) | Configure the workspace, models, Service, Jobs, Components, plugins, and CLI overrides. | +| [Services and Deployment](docs/en/services.md) | Use HTTP, SSE, MCP, and Studio while respecting the default security boundary. | | [Memory as File](docs/en/memory_as_file.md) | Understand workspace layers, frontmatter, wikilinks, chunks, and the file-as-source-of-truth model. | | [Auto Memory](docs/en/auto_memory.md) | Preserve source conversations and distill reusable daily memory cards. | | [Auto Resource](docs/en/auto_resource.md) | Import supported text and image resources as source-linked daily cards. | @@ -347,7 +349,9 @@ These guides cover the main user workflows and the runtime contracts implemented | [Application Scenarios](docs/en/reme_scene.md) | Follow concrete financial research, coding-memory, and personal knowledge-base examples. | | [Framework](docs/en/framework.md) | Understand Application, Job, Step, Component, service, configuration, and lifecycle boundaries. | | [TypeScript integrations](typescript/README.md) | Configure the shared client and native DeepSeek Harness and OpenClaw adapters. | -| [ReMe Blog](https://agentscope-ai.github.io/ReMe/?doc=en-reme-blog) | Read the product story, design rationale, examples, and benchmark summary. | +| [CLI and Job API](docs/en/reference/cli.md) | Learn command syntax and use the generated default Job parameter reference. | +| [Operations and Recovery](docs/en/operations.md) | Diagnose services, maintain indexes, and back up, migrate, or recover a workspace. | +| [ReMe Blog](https://reme.agentscope.io/en/reme-blog) | Read the product story, design rationale, examples, and benchmark summary. | ## 🛠️ Common Commands diff --git a/README_ZH.md b/README_ZH.md index bd7b6519..7de559a1 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -49,13 +49,13 @@ - [2026.08] - 发布 [`@agentscope-ai/reme`](https://www.npmjs.com/package/@agentscope-ai/reme),提供统一 TypeScript HTTP client,以及 DeepSeek Harness 和 OpenClaw 的原生 ReMe 记忆集成。 -- [2026.08] - 发布 [ReMe 博客](https://agentscope-ai.github.io/ReMe/?doc=zh-reme-blog),系统介绍本地优先的记忆架构、自进化工作流、混合检索、 +- [2026.08] - 发布 [ReMe 博客](https://reme.agentscope.io/zh/reme-blog),系统介绍本地优先的记忆架构、自进化工作流、混合检索、 主动发现与评测结果。 - [2026.08] - 基于 ReMe 的智能体工具使用 - [经验驱动增强方法](https://reme.agentscope.io/?doc=toolmemory-zh)已发布,见 + [经验驱动增强方法](https://reme.agentscope.io/zh/benchmarks/toolmemory)已发布,见 [arXiv:2608.03403](https://arxiv.org/abs/2608.03403)。 -- [2026.07] - 新增可选插件:[每日论文](https://reme.agentscope.io/?doc=daily-paper-zh)用于论文发现与解析, - [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-zh)用于研究最近 24 小时的主题相关财联社新闻,通过本地记忆搜索回顾历史材料并构建 +- [2026.07] - 新增可选插件:[每日论文](https://reme.agentscope.io/zh/plugins/daily-paper)用于论文发现与解析, + [Auto Fin](https://reme.agentscope.io/zh/plugins/auto-fin)用于研究最近 24 小时的主题相关财联社新闻,通过本地记忆搜索回顾历史材料并构建 wikilink。 - [2026.07] - 我们的论文 [Remember Me, Refine Me: A Dynamic Procedural Memory Framework for Experience-Driven Agent Evolution](https://aclanthology.org/2026.findings-acl.829/) @@ -170,7 +170,7 @@ ReMe 会把 Agent 记忆保存为可读的 Markdown。 上面的 `core` 安装已包含 Studio。启动 ReMe 后,打开 即可浏览、编辑和搜索 workspace。 如需为基础安装单独添加 Studio,可使用 `pip install "reme-ai[web]"`。源码构建、配置和开发说明见 -[ReMe Studio 指南](https://reme.agentscope.io/?doc=studio-zh)。 +[ReMe Studio 指南](https://reme.agentscope.io/zh/workspace/studio)。 ## 🤝 将 ReMe 接入你的 Agent @@ -301,11 +301,11 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上 | 基准 | 设置 | 样本量 | Agentic 得分 | 主要检验内容 | | --------------------------------------------------------------------------- | ----------- | ----------------: | -----------: | ------------------------------ | -| **[LongMemEval cleaned-s](https://reme.agentscope.io/?doc=longmemeval-zh)** | **整体** | **500 题** | **89.4%** | 跨会话检索、知识更新与时间推理 | -| [BEAM](https://reme.agentscope.io/?doc=beam-zh) | 100K 上下文 | 20 cases / 400 题 | 66.1% | 十类长上下文记忆任务 | -| [BEAM](https://reme.agentscope.io/?doc=beam-zh) | 1M 上下文 | 35 cases / 700 题 | 65.0% | 超长对话设置 | +| **[LongMemEval cleaned-s](https://reme.agentscope.io/zh/benchmarks/longmemeval)** | **整体** | **500 题** | **89.4%** | 跨会话检索、知识更新与时间推理 | +| [BEAM](https://reme.agentscope.io/zh/benchmarks/beam) | 100K 上下文 | 20 cases / 400 题 | 66.1% | 十类长上下文记忆任务 | +| [BEAM](https://reme.agentscope.io/zh/benchmarks/beam) | 1M 上下文 | 35 cases / 700 题 | 65.0% | 超长对话设置 | -在仓库的 [π-Bench 评测](https://reme.agentscope.io/?doc=pibench-zh)中,ReMe Agent 在 5 种用户角色上的平均 **PROC 得分为 0.580** +在仓库的 [π-Bench 评测](https://reme.agentscope.io/zh/benchmarks/pibench)中,ReMe Agent 在 5 种用户角色上的平均 **PROC 得分为 0.580** ,比相同测试模型配置的 NanoBot 高 2.4%。PROC 用于评估隐藏意图完成、针对性澄清、跨会话偏好和规范复用、跨任务依赖推断以及欠规格请求推进等主动性能力。 ## 🧩 扩展与插件 @@ -316,8 +316,8 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上 | 插件 | 能力 | | ---------------------------------------------------------- | ------------------------------------------------------------------------------ | -| [每日论文](https://reme.agentscope.io/?doc=daily-paper-zh) | 发现并排序论文,使用 Agent 解读 PDF,生成文件化论文笔记和五分钟简报。 | -| [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-zh) | 拉取主题相关财联社新闻,搜索 ReMe 历史材料并生成带 wikilink 的 Markdown 报告。 | +| [每日论文](https://reme.agentscope.io/zh/plugins/daily-paper) | 发现并排序论文,使用 Agent 解读 PDF,生成文件化论文笔记和五分钟简报。 | +| [Auto Fin](https://reme.agentscope.io/zh/plugins/auto-fin) | 拉取主题相关财联社新闻,搜索 ReMe 历史材料并生成带 wikilink 的 Markdown 报告。 | 安装、查看、校验、启用和卸载 ReMe 插件的方法见[插件管理](docs/zh/plugin_management.md)。 @@ -328,6 +328,8 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上 | 文档 | 主要内容 | | ------------------------------------------------------------------------ | ---------------------------------------------------------------------- | | [快速开始](docs/zh/quick_start.md) | 安装 ReMe、启动服务,并执行首次文件和记忆操作。 | +| [基础配置](docs/zh/configuration.md) | 配置 workspace、模型、Service、Job、Component、插件和命令行覆盖。 | +| [服务与部署](docs/zh/services.md) | 使用 HTTP、SSE、MCP 和 Studio,并理解默认安全边界。 | | [Memory as File](docs/zh/memory_as_file.md) | 理解 workspace 分层、frontmatter、wikilink、chunk 和文件事实来源模型。 | | [Auto Memory](docs/zh/auto_memory.md) | 保留过滤后的对话来源记录,并提炼可复用的 daily 记忆卡片。 | | [Auto Resource](docs/zh/auto_resource.md) | 导入支持的文本与图像资料,转换为可追溯来源的 daily 卡片。 | @@ -337,7 +339,9 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上 | [应用场景](docs/zh/reme_scene.md) | 查看金融研究、研发记忆和个人知识库的完整使用示例。 | | [框架说明](docs/zh/framework.md) | 理解 Application、Job、Step、Component、service、配置和生命周期边界。 | | [TypeScript 集成](typescript/README_ZH.md) | 配置统一 client,以及 DeepSeek Harness 和 OpenClaw 原生适配器。 | -| [ReMe 博客](https://agentscope-ai.github.io/ReMe/?doc=zh-reme-blog) | 了解完整产品故事、设计动机、使用示例和评测摘要。 | +| [CLI 与 Job API](docs/zh/reference/cli.md) | 查询命令语法,以及由默认配置自动生成的 Job 参数参考。 | +| [运维与恢复](docs/zh/operations.md) | 诊断服务、维护索引,并备份、迁移和恢复 workspace。 | +| [ReMe 博客](https://reme.agentscope.io/zh/reme-blog) | 了解完整产品故事、设计动机、使用示例和评测摘要。 | ## 🛠️ 常用命令 diff --git a/benchmark/beam/README.md b/benchmark/beam/README.md index aef8a3b7..014427c2 100644 --- a/benchmark/beam/README.md +++ b/benchmark/beam/README.md @@ -15,8 +15,20 @@ include abstention, contradiction resolution, event ordering, information extraction, instruction following, knowledge update, multi-session reasoning, preference following, summarization, and temporal reasoning. -> For the shared setup (dependencies, credentials, log conventions) see the -> [top-level benchmark README](../README.md). +Install ReMe and the BEAM plugin in editable mode from the repository root: + +```bash +python -m pip install -e ".[as]" +reme plugins install ./plugins/beam --editable +reme plugins validate beam +``` + +The runner explicitly enables the installed `beam` plugin and combines its defaults with +ReMe's built-in `benchmark` preset. Editable installation keeps changes under +[`plugins/beam`](../../plugins/beam/README.md) visible without reinstalling the plugin. +Custom application config paths still work through `reme.config` and can use `extends: benchmark`. +This directory continues to own the runner, evaluation settings, dataset and outputs. +Model credentials use the environment variables declared by the shared benchmark configuration. ## 1. Get the Dataset @@ -59,7 +71,7 @@ python benchmark/beam/run.py --eval_only # reuse existing workspac | `dataset.start_index` / `num_items` | Case pagination (`num_items` `0` = all). | | `dataset.workspace_root` | Per-case workspace root (`benchmark/beam/workspaces/beam`). | | `evaluation.num_workers` | `0` = auto, `1` = sequential, `>1` = parallel. | -| `reme.config` | ReMe config used (`beam.yaml`). | +| `reme.config` | ReMe config used (`benchmark`). | | `output.dir` | Results directory (`benchmark/beam/results`). | ## 5. Outputs diff --git a/benchmark/beam/README_ZH.md b/benchmark/beam/README_ZH.md index f562193b..1c063497 100644 --- a/benchmark/beam/README_ZH.md +++ b/benchmark/beam/README_ZH.md @@ -13,7 +13,19 @@ ordering(事件排序)、information extraction(信息抽取)、instruct knowledge update(知识更新)、multi-session reasoning(多会话推理)、preference following (偏好遵循)、summarization(摘要)与 temporal reasoning(时间推理)。 -> 公共设置(依赖、凭据、日志约定)见[总评测说明](../README_ZH.md)。 +在仓库根目录以 editable 模式安装 ReMe 和 BEAM 插件: + +```bash +python -m pip install -e ".[as]" +reme plugins install ./plugins/beam --editable +reme plugins validate beam +``` + +runner 显式启用已安装的 `beam` 插件,并将插件默认配置与 ReMe 内置的 `benchmark` 配置组合。 +editable 安装会让 [`plugins/beam`](../../plugins/beam/README_ZH.md) 下的源码修改直接生效,无需重复安装。 +本目录继续保留评测参数、数据集及输出。自定义完整应用配置路径仍可通过 `reme.config` 指定, +并可使用 `extends: benchmark`。 +模型凭据通过公共 benchmark 配置中声明的环境变量设置。 ## 1. 获取数据集 @@ -55,7 +67,7 @@ python benchmark/beam/run.py --eval_only # 复用已有工作区 | `dataset.start_index` / `num_items` | case 分页(`num_items` 为 `0` 表示全部)。 | | `dataset.workspace_root` | case 工作区根目录(`benchmark/beam/workspaces/beam`)。 | | `evaluation.num_workers` | `0` = 自动,`1` = 串行,`>1` = 并行。 | -| `reme.config` | 使用的 ReMe 配置(`beam.yaml`)。 | +| `reme.config` | 使用的 ReMe 配置(`benchmark`)。 | | `output.dir` | 结果目录(`benchmark/beam/results`)。 | ## 5. 输出 diff --git a/benchmark/beam/config.yaml b/benchmark/beam/config.yaml index 423697a5..cca633b8 100644 --- a/benchmark/beam/config.yaml +++ b/benchmark/beam/config.yaml @@ -14,7 +14,7 @@ evaluation: compress_session: false # true = compress session chunks in search_v2 (query-aware); false = no compression reme: - config: "beam.yaml" # reme config (in reme/config/) + config: "benchmark" # shared ReMe benchmark preset; runner enables the installed beam plugin output: dir: "benchmark/beam/results" diff --git a/benchmark/beam/run.py b/benchmark/beam/run.py index 06e35b7d..b49284fd 100644 --- a/benchmark/beam/run.py +++ b/benchmark/beam/run.py @@ -29,7 +29,7 @@ import yaml from dotenv import load_dotenv # Load .env from project root -_PROJECT_ROOT = Path(__file__).parent.parent.parent +_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent load_dotenv(_PROJECT_ROOT / ".env") # Workspace root — read from config.yaml (dataset.workspace_root) @@ -151,6 +151,22 @@ def load_eval_config(config_path: str | None = None) -> dict: return yaml.safe_load(raw) +def create_reme_app(config: str = "benchmark", **overrides): + """Create an app with the installed BEAM plugin explicitly enabled. + + Plugin discovery remains environment-based; editable installation keeps local + plugin source changes visible to every multiprocessing worker. + """ + from reme import Application + from reme.config import resolve_app_config + + enabled_plugins = list(overrides.pop("plugins", ()) or ()) + if "beam" not in enabled_plugins: + enabled_plugins.append("beam") + app_config = resolve_app_config(config=config, plugins=enabled_plugins, **overrides) + return Application(**app_config) + + # --------------------------------------------------------------------------- # BEAM data loading # --------------------------------------------------------------------------- @@ -319,8 +335,6 @@ async def evaluate_case(eval_config: dict, case_id: str, eval_only: bool = False Returns: A results dict with all questions, answers, and judgments. """ - from reme import Application - from reme.config import resolve_app_config dataset_cfg = eval_config["dataset"] chat_size = dataset_cfg["chat_size"] @@ -375,7 +389,7 @@ async def evaluate_case(eval_config: dict, case_id: str, eval_only: bool = False force_init=True, ) - cfg = resolve_app_config( + app = create_reme_app( config=eval_config["reme"]["config"], workspace_dir=workspace_dir, log_to_console=output_cfg.get("log_to_console", True), @@ -383,7 +397,6 @@ async def evaluate_case(eval_config: dict, case_id: str, eval_only: bool = False enable_logo=False, ) - app = Application(**cfg) await app.start() from reme.utils.evaluation_interface import check_agent_token_usage # noqa: E402 diff --git a/benchmark/longmemeval/README.md b/benchmark/longmemeval/README.md index 070f921e..7f07d170 100644 --- a/benchmark/longmemeval/README.md +++ b/benchmark/longmemeval/README.md @@ -12,8 +12,20 @@ agentic (ReAct) mode, and scores the answer with an LLM-as-judge. Question types include single-session (user / assistant / preference), multi-session reasoning, knowledge update, and temporal reasoning. -> For the shared setup (dependencies, credentials, log conventions) see the -> [top-level benchmark README](../README.md). +Install ReMe and the LongMemEval plugin in editable mode from the repository root: + +```bash +python -m pip install -e ".[as]" +reme plugins install ./plugins/lme --editable +reme plugins validate lme +``` + +The runner explicitly enables the installed `lme` plugin and combines its defaults with +ReMe's built-in `benchmark` preset. Editable installation keeps changes under +[`plugins/lme`](../../plugins/lme/README.md) visible without reinstalling the plugin. +Custom application config paths still work through `reme.config` and can use `extends: benchmark`. +This directory continues to own the runner, evaluation settings, dataset and outputs. +Model credentials use the environment variables declared by the shared benchmark configuration. ## 1. Get the Dataset @@ -46,7 +58,8 @@ python benchmark/longmemeval/run.py --eval_only # reuse existing w 1. Load the dataset (ground truth is embedded in the data file). 2. For each item, create an isolated workspace and ingest sessions in chronological order. -3. Trigger `auto_dream` when consecutive sessions cross the configured hour (default 23:00). +3. If a custom application configuration enables `auto_dream`, trigger it when sessions cross the configured hour + (default 23:00). The packaged preset leaves it disabled. 4. Answer each question via agentic (ReAct) mode. 5. Judge the answer (binary yes/no) with the `answer_judge` job and print per-type accuracy. @@ -60,7 +73,7 @@ python benchmark/longmemeval/run.py --eval_only # reuse existing w | `dataset.workspace_root` | Per-item workspace root (`benchmark/longmemeval/workspaces/longmemeval-s`). | | `evaluation.num_workers` | `0` = auto (cpu-2), `1` = sequential, `>1` = parallel. | | `evaluation.filter_future_sessions` | Only ingest sessions with timestamp ≤ `question_date`. | -| `reme.config` | ReMe config used (`lme.yaml`). | +| `reme.config` | ReMe config used (`benchmark`). | | `reme.dream_trigger_hour` / `dream_scan_days` / `dream_max_units` | Dream triggering behavior. | | `output.dir` | Results directory (`benchmark/longmemeval/results`). | @@ -93,4 +106,4 @@ agentscope==2.0.4.post1, conda reme env, 32 workers, eval-only (reusing prebuilt | single-session-preference | 0.633 | 36,802 | 818 | 37,620 | 3.60 | | single-session-user | 0.986 | 27,433 | 359 | 27,792 | 2.60 | | temporal-reasoning | 0.902 | 62,674 | 985 | 63,659 | 4.97 | -| **OVERALL** | **0.894** | **43,448** | **876** | **44,324** | **3.69** | \ No newline at end of file +| **OVERALL** | **0.894** | **43,448** | **876** | **44,324** | **3.69** | diff --git a/benchmark/longmemeval/README_ZH.md b/benchmark/longmemeval/README_ZH.md index 9c07b175..4c66df67 100644 --- a/benchmark/longmemeval/README_ZH.md +++ b/benchmark/longmemeval/README_ZH.md @@ -8,7 +8,19 @@ LongMemEval 是一个面向**多轮多会话历史的长期记忆能力**的评 题型包括单会话(user / assistant / preference)、多会话推理、知识更新与时间推理等。 -> 公共设置(依赖、凭据、日志约定)见[总评测说明](../README_ZH.md)。 +在仓库根目录以 editable 模式安装 ReMe 和 LongMemEval 插件: + +```bash +python -m pip install -e ".[as]" +reme plugins install ./plugins/lme --editable +reme plugins validate lme +``` + +runner 显式启用已安装的 `lme` 插件,并将插件默认配置与 ReMe 内置的 `benchmark` 配置组合。 +editable 安装会让 [`plugins/lme`](../../plugins/lme/README_ZH.md) 下的源码修改直接生效,无需重复安装。 +本目录继续保留评测参数、数据集及输出。自定义完整应用配置路径仍可通过 `reme.config` 指定, +并可使用 `extends: benchmark`。 +模型凭据通过公共 benchmark 配置中声明的环境变量设置。 ## 1. 获取数据集 @@ -41,7 +53,7 @@ python benchmark/longmemeval/run.py --eval_only # 复用已有工 1. 加载数据集(ground truth 已内嵌在数据文件中)。 2. 为每个条目创建独立工作区,按时间顺序摄入会话。 -3. 当相邻会话跨越配置的时刻(默认 23:00)时触发 `auto_dream`。 +3. 若自定义应用配置启用了 `auto_dream`,在相邻会话跨越配置时刻(默认 23:00)时触发;插件预设保持关闭。 4. 以 agentic(ReAct)模式回答每个问题。 5. 通过 `answer_judge` 任务对答案做二元(yes/no)评判,并输出各类型准确率。 @@ -55,7 +67,7 @@ python benchmark/longmemeval/run.py --eval_only # 复用已有工 | `dataset.workspace_root` | 条目工作区根目录(`benchmark/longmemeval/workspaces/longmemeval-s`)。 | | `evaluation.num_workers` | `0` = 自动(cpu-2),`1` = 串行,`>1` = 并行。 | | `evaluation.filter_future_sessions` | 仅摄入时间戳 ≤ `question_date` 的会话。 | -| `reme.config` | 使用的 ReMe 配置(`lme.yaml`)。 | +| `reme.config` | 使用的 ReMe 配置(`benchmark`)。 | | `reme.dream_trigger_hour` / `dream_scan_days` / `dream_max_units` | dream 触发行为。 | | `output.dir` | 结果目录(`benchmark/longmemeval/results`)。 | diff --git a/benchmark/longmemeval/config.yaml b/benchmark/longmemeval/config.yaml index 72ee7b75..36260479 100644 --- a/benchmark/longmemeval/config.yaml +++ b/benchmark/longmemeval/config.yaml @@ -10,7 +10,7 @@ dataset: workspace_root: "benchmark/longmemeval/workspaces/longmemeval-s" # workspace root for item workspaces evaluation: - # LLM-as-judge uses the 'judge' as_llm component defined in lme.yaml + # LLM-as-judge uses the 'judge' as_llm component defined in benchmark.yaml # Model and credentials are configured there (reading from .env) # Judgment is always binary (yes/no) — defined in lme/llm_judge.yaml num_workers: 32 # 0 = auto (cpu_count - 2, min 1); 1 = sequential; >1 = parallel @@ -18,7 +18,7 @@ evaluation: compress_session: false # true = compress session chunks in search_v2 (query-aware); false = no compression reme: - config: "lme.yaml" # reme config to use (in reme/config/) + config: "benchmark" # shared ReMe benchmark preset; runner enables the installed lme plugin # Dream trigger: when gap between consecutive sessions crosses this hour (23:00) dream_trigger_hour: 23 # Dream scan_days for each trigger diff --git a/benchmark/longmemeval/run.py b/benchmark/longmemeval/run.py index 9e48af43..97b10ae4 100644 --- a/benchmark/longmemeval/run.py +++ b/benchmark/longmemeval/run.py @@ -28,7 +28,7 @@ import yaml from dotenv import load_dotenv # Load .env from project root -_PROJECT_ROOT = Path(__file__).parent.parent.parent +_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent load_dotenv(_PROJECT_ROOT / ".env") # Workspace root for evaluation items — read from config.yaml (dataset.workspace_root) @@ -150,6 +150,22 @@ def load_eval_config(config_path: str | None = None) -> dict: return yaml.safe_load(raw) +def create_reme_app(config: str = "benchmark", **overrides): + """Create an app with the installed LongMemEval plugin explicitly enabled. + + Plugin discovery remains environment-based; editable installation keeps local + plugin source changes visible to every multiprocessing worker. + """ + from reme import Application + from reme.config import resolve_app_config + + enabled_plugins = list(overrides.pop("plugins", ()) or ()) + if "lme" not in enabled_plugins: + enabled_plugins.append("lme") + app_config = resolve_app_config(config=config, plugins=enabled_plugins, **overrides) + return Application(**app_config) + + # --------------------------------------------------------------------------- # Date utilities # --------------------------------------------------------------------------- @@ -257,8 +273,6 @@ async def evaluate_item(item: dict, eval_config: dict, item_index: int, eval_onl using the existing workspace. Useful for re-evaluating different query configurations without re-ingesting sessions. """ - from reme import Application - from reme.config import resolve_app_config from reme.utils.evaluation_interface import track_agent_token_usage, track_job_counts reme_cfg = eval_config["reme"] @@ -325,7 +339,7 @@ async def evaluate_item(item: dict, eval_config: dict, item_index: int, eval_onl force_init=True, ) - cfg = resolve_app_config( + app = create_reme_app( config=reme_cfg["config"], workspace_dir=workspace_dir, log_to_console=output_cfg.get("log_to_console", True), @@ -333,7 +347,6 @@ async def evaluate_item(item: dict, eval_config: dict, item_index: int, eval_onl enable_logo=False, ) - app = Application(**cfg) await app.start() try: diff --git a/benchmark/pibench/README.md b/benchmark/pibench/README.md index c62249a0..858e689c 100644 --- a/benchmark/pibench/README.md +++ b/benchmark/pibench/README.md @@ -289,7 +289,7 @@ removed"). They need the executed tool calls in the trace. The pipeline: | user_agent / judger models | `config/models/reme.yaml` | | Agent system prompt | `bridge_reme.py` `build_system_prompt()` | | Memory retrieval limit/threshold | `--search-limit/--search-min-score` on the bridge command in `run_persona.sh` | -| ReMe internal parameters | **Do not modify ReMe source**; write a dedicated config modeled on `reme/config/beam.yaml` and override via `resolve_app_config(config=...)` (see bridge `_init_reme_app`) | +| ReMe internal parameters | **Do not modify ReMe source**; extend the built-in `benchmark` config and override via `resolve_app_config(config=...)` (see bridge `_init_reme_app`) | | Turn timeout / tool iteration cap | `config/models/reme.yaml` `run.turn_timeout`, `model.max_tool_iterations` | ## 11. Troubleshooting diff --git a/benchmark/pibench/README_ZH.md b/benchmark/pibench/README_ZH.md index 0a8b58d6..5e4668b2 100644 --- a/benchmark/pibench/README_ZH.md +++ b/benchmark/pibench/README_ZH.md @@ -255,7 +255,7 @@ grep -h "overall_average_score\|overall_proactiveness" \ | user_agent / judger 模型 | `config/models/reme.yaml` | | agent system prompt | `bridge_reme.py` `build_system_prompt()` | | 记忆检索条数/阈值 | `run_persona.sh` bridge 启动命令的 `--search-limit/--search-min-score` | -| ReMe 内部参数 | **不要改 ReMe 源码**;仿照 `reme/config/beam.yaml` 写专有配置,经 `resolve_app_config(config=...)` 覆盖(见 bridge `_init_reme_app`) | +| ReMe 内部参数 | **不要改 ReMe 源码**;继承内置 `benchmark` 配置,并经 `resolve_app_config(config=...)` 覆盖(见 bridge `_init_reme_app`) | | 轮超时/工具迭代上限 | `config/models/reme.yaml` `run.turn_timeout`、`model.max_tool_iterations` | ## 11. 故障排查 diff --git a/benchmark/toolmemory/README.md b/benchmark/toolmemory/README.md index 7f358018..40ac47fe 100644 --- a/benchmark/toolmemory/README.md +++ b/benchmark/toolmemory/README.md @@ -6,7 +6,7 @@ > Code: [https://github.com/WangCan1178/ExpG](https://github.com/WangCan1178/ExpG)

- ExpG challenges and overview + ExpG challenges and overview

### Overview diff --git a/benchmark/toolmemory/README_ZH.md b/benchmark/toolmemory/README_ZH.md index b0c5b89b..0d247385 100644 --- a/benchmark/toolmemory/README_ZH.md +++ b/benchmark/toolmemory/README_ZH.md @@ -6,7 +6,7 @@ > 代码:[https://github.com/WangCan1178/ExpG](https://github.com/WangCan1178/ExpG)

- ExpG 挑战与概览 + ExpG 挑战与概览

### 简介 diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 00000000..697aea53 --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,344 @@ +import fs from "node:fs"; +import path from "node:path"; +import { execFileSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; +import { defineConfig, type DefaultTheme } from "vitepress"; +import { legacyRoutes } from "./legacy-routes.mjs"; + +const sourceRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const repositoryRoot = path.resolve(sourceRoot, "../../.."); +const repository = "https://github.com/agentscope-ai/ReMe"; +const base = process.env.DOCS_BASE || "/"; + +function readSourceMap(): Record { + try { + return JSON.parse(fs.readFileSync(path.join(sourceRoot, ".source-map.json"), "utf8")); + } catch { + return {}; + } +} + +const sourceMap = readSourceMap(); + +function collectMarkdown(directory: string, root = directory): string[] { + const files: string[] = []; + for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { + if (entry.name.startsWith(".") || entry.name === "public" || entry.name === "figure") continue; + const absolute = path.join(directory, entry.name); + if (entry.isDirectory()) files.push(...collectMarkdown(absolute, root)); + else if (entry.name.endsWith(".md")) files.push(path.relative(root, absolute).replaceAll(path.sep, "/")); + } + return files.sort(); +} + +function buildLlmsFiles(outDir: string) { + const pages = collectMarkdown(sourceRoot); + const index = [ + "# ReMe Documentation", + "", + "> Local-first, file-native memory for agents.", + "", + ...pages.map((relativePath) => { + const source = fs.readFileSync(path.join(sourceRoot, relativePath), "utf8"); + const title = source.match(/^#\s+(.+)$/m)?.[1] + || source.match(/^title:\s*(.+)$/m)?.[1] + || path.basename(relativePath, ".md"); + const route = relativePath.replace(/(?:^|\/)index\.md$/, "").replace(/\.md$/, ""); + return `- [${title}](https://reme.agentscope.io/${route})`; + }), + "", + ]; + fs.writeFileSync(path.join(outDir, "llms.txt"), index.join("\n"), "utf8"); + + const full = ["# ReMe Documentation", ""]; + for (const relativePath of pages) { + const source = fs.readFileSync(path.join(sourceRoot, relativePath), "utf8"); + full.push(``, "", source, "", "---", ""); + const pageDir = path.join(outDir, relativePath.replace(/\.md$/, "")); + fs.mkdirSync(pageDir, { recursive: true }); + fs.writeFileSync(path.join(pageDir, "llms.txt"), source, "utf8"); + } + fs.writeFileSync(path.join(outDir, "llms-full.txt"), full.join("\n"), "utf8"); +} + +function sourcePathFor(relativePath: string) { + return sourceMap[relativePath] || `docs/${relativePath}`; +} + +function sourceLastUpdated(relativePath: string): number | undefined { + const sourcePath = sourcePathFor(relativePath); + try { + const timestamp = execFileSync("git", ["log", "-1", "--format=%ct", "--", sourcePath], { + cwd: repositoryRoot, + encoding: "utf8", + }).trim(); + if (timestamp) return Number(timestamp) * 1000; + } catch { + // Fall back to the canonical file timestamp outside a Git checkout. + } + try { + return fs.statSync(path.join(repositoryRoot, sourcePath)).mtimeMs; + } catch { + return undefined; + } +} + +const legacyRedirectScript = `(() => { + const routes = ${JSON.stringify(legacyRoutes)}; + const id = new URLSearchParams(window.location.search).get("doc"); + const target = id && routes[id]; + const base = ${JSON.stringify(base)}; + if (target) { + const destination = /^https?:/.test(target) + ? target + : base.replace(/\\/$/, "") + target; + window.location.replace(destination + window.location.hash); + return; + } + const root = base.endsWith("/") ? base : base + "/"; + if (window.location.pathname === root) { + window.location.replace(root + "zh/" + window.location.hash); + } +})();`; + +function nav(language: "zh" | "en"): DefaultTheme.NavItem[] { + const zh = language === "zh"; + return [ + { text: zh ? "开始使用" : "Get Started", link: `/${language}/quick_start` }, + { text: zh ? "核心概念" : "Concepts", link: `/${language}/memory_as_file` }, + { text: zh ? "指南" : "Guides", link: `/${language}/auto_memory` }, + { text: zh ? "集成" : "Integrations", link: `/${language}/integrations` }, + { text: zh ? "API 参考" : "API Reference", link: `/${language}/reference/cli` }, + { text: zh ? "常见问题" : "FAQ", link: `/${language}/faq` }, + ]; +} + +function sidebar(language: "zh" | "en"): DefaultTheme.SidebarItem[] { + const zh = language === "zh"; + return [ + { + text: zh ? "开始使用" : "Get Started", + collapsed: false, + items: [ + { text: zh ? "项目介绍" : "Introduction", link: `/${language}/` }, + { text: zh ? "快速开始" : "Quick Start", link: `/${language}/quick_start` }, + { text: zh ? "基础配置" : "Configuration", link: `/${language}/configuration` }, + { text: zh ? "服务与部署" : "Services and Deployment", link: `/${language}/services` }, + ], + }, + { + text: zh ? "核心概念" : "Core Concepts", + collapsed: false, + items: [ + { text: zh ? "文件即记忆" : "Memory as File", link: `/${language}/memory_as_file` }, + { text: zh ? "记忆检索" : "Memory Search", link: `/${language}/memory_search` }, + { text: zh ? "自动关联" : "Auto Link", link: `/${language}/auto_link` }, + { text: zh ? "应用场景" : "Application Scenarios", link: `/${language}/reme_scene` }, + ], + }, + { + text: zh ? "记忆工作流" : "Memory Workflows", + collapsed: false, + items: [ + { text: "Auto Memory", link: `/${language}/auto_memory` }, + { text: "Auto Resource", link: `/${language}/auto_resource` }, + { text: "Auto Dream", link: `/${language}/auto_dream` }, + { text: "Proactive", link: `/${language}/proactive` }, + ], + }, + { + text: zh ? "Agent 集成" : "Agent Integrations", + collapsed: true, + items: [ + { text: zh ? "集成总览" : "Overview", link: `/${language}/integrations` }, + { text: "Claude Code", link: `/${language}/integrations/claude-code` }, + { text: "Hermes Agent", link: `/${language}/integrations/hermes` }, + { text: zh ? "TypeScript 客户端" : "TypeScript Client", link: `/${language}/integrations/typescript` }, + { text: "DeepSeek Harness", link: `/${language}/integrations/dsh` }, + { text: "OpenClaw", link: `/${language}/integrations/openclaw` }, + ], + }, + { + text: zh ? "工作区与插件" : "Workspace and Plugins", + collapsed: true, + items: [ + { text: "ReMe Studio", link: `/${language}/workspace/studio` }, + { text: zh ? "插件管理" : "Plugin Management", link: `/${language}/plugin_management` }, + { text: zh ? "插件开发" : "Plugin Development", link: `/${language}/plugin_development` }, + { text: zh ? "每日论文" : "Daily Paper", link: `/${language}/plugins/daily-paper` }, + { text: "Auto Fin", link: `/${language}/plugins/auto-fin` }, + { text: "LME", link: `/${language}/plugins/lme` }, + { text: "BEAM", link: `/${language}/plugins/beam` }, + ], + }, + { + text: zh ? "API 参考" : "API Reference", + collapsed: true, + items: [ + { text: "CLI", link: `/${language}/reference/cli` }, + { text: zh ? "Job API" : "Job API", link: `/${language}/reference/jobs` }, + { text: "HTTP / MCP", link: `/${language}/services#http-api` }, + ], + }, + { + text: zh ? "运维" : "Operations", + collapsed: true, + items: [ + { text: zh ? "诊断、备份与恢复" : "Diagnostics, Backup, and Recovery", link: `/${language}/operations` }, + { text: zh ? "常见问题" : "FAQ", link: `/${language}/faq` }, + ], + }, + { + text: zh ? "开发者" : "Development", + collapsed: true, + items: [ + { text: zh ? "代码框架" : "Framework", link: `/${language}/framework` }, + { text: zh ? "开源与贡献" : "Contributing", link: `/${language}/contributing` }, + ], + }, + { + text: zh ? "评测" : "Benchmarks", + collapsed: true, + items: [ + { text: "BEAM", link: `/${language}/benchmarks/beam` }, + { text: "LongMemEval", link: `/${language}/benchmarks/longmemeval` }, + { text: "π-Bench", link: `/${language}/benchmarks/pibench` }, + { text: "Tool Memory / ExpG", link: `/${language}/benchmarks/toolmemory` }, + ], + }, + ]; +} + +function configureRepositoryLinks(md: any) { + for (const ruleName of ["link_open", "image"] as const) { + const original = md.renderer.rules[ruleName]; + md.renderer.rules[ruleName] = (tokens: any[], index: number, options: any, env: any, self: any) => { + const attribute = ruleName === "image" ? "src" : "href"; + const token = tokens[index]; + const attributeIndex = token.attrIndex(attribute); + const target = attributeIndex >= 0 ? token.attrs[attributeIndex][1] : ""; + if (target && !/^(?:[a-z]+:|#|\/)/i.test(target)) { + const cleanTarget = target.split("#")[0].split("?")[0]; + const generatedTarget = path.resolve(sourceRoot, path.dirname(env.relativePath), cleanTarget); + if (!fs.existsSync(generatedTarget)) { + const originalPage = sourcePathFor(env.relativePath); + const originalTarget = path.posix.normalize(path.posix.join(path.posix.dirname(originalPage), cleanTarget)); + const suffix = target.slice(cleanTarget.length); + const url = ruleName === "image" + ? `https://raw.githubusercontent.com/agentscope-ai/ReMe/main/${originalTarget}${suffix}` + : `${repository}/blob/main/${originalTarget}${suffix}`; + token.attrs[attributeIndex][1] = url; + } + } + return original ? original(tokens, index, options, env, self) : self.renderToken(tokens, index, options); + }; + } +} + +export default defineConfig({ + lang: "zh-CN", + title: "ReMe", + description: "Local-first, file-native memory for agents", + base, + cleanUrls: true, + lastUpdated: true, + ignoreDeadLinks: [/^http:\/\/localhost(?::\d+)?(?:\/|$)/], + sitemap: { + hostname: "https://reme.agentscope.io", + transformItems(items) { + const isRoot = (url: string) => url.replace(/^\/+|\/+$/g, "") === ""; + return items.filter((item) => !isRoot(item.url)).map((item) => { + const route = item.url.replace(/^\/+/, ""); + const relativePath = !route || route.endsWith("/") ? `${route}index.md` : `${route}.md`; + const links = item.links?.filter((link) => !isRoot(link.url)); + return { ...item, links, lastmod: sourceLastUpdated(relativePath) }; + }); + }, + }, + head: [ + ["link", { rel: "icon", type: "image/svg+xml", href: `${base}reme-icon.svg` }], + ["meta", { name: "theme-color", content: "#087f6a" }], + ["script", {}, legacyRedirectScript], + ], + markdown: { + config: configureRepositoryLinks, + }, + transformPageData(pageData, { siteConfig }) { + const sourcePath = path.join(siteConfig.srcDir, pageData.relativePath); + pageData.frontmatter._sourcePath = sourcePathFor(pageData.relativePath); + pageData.lastUpdated = sourceLastUpdated(pageData.relativePath); + try { + pageData.frontmatter._rawMarkdown = fs.readFileSync(sourcePath, "utf8"); + } catch { + pageData.frontmatter._rawMarkdown = ""; + } + }, + buildEnd(siteConfig) { + buildLlmsFiles(siteConfig.outDir); + }, + themeConfig: { + logo: "/reme-icon.svg", + siteTitle: "ReMe", + nav: [ + ...nav("zh"), + { + text: "语言", + items: [ + { text: "简体中文", link: "/zh/" }, + { text: "English", link: "/en/" }, + ], + }, + ], + outline: { label: "页面导航", level: [2, 3] }, + search: { + provider: "local", + options: { + locales: { + zh: { + translations: { + button: { buttonText: "搜索文档", buttonAriaLabel: "搜索文档" }, + modal: { + noResultsText: "没有找到相关内容", + resetButtonTitle: "清除查询", + footer: { selectText: "选择", navigateText: "切换", closeText: "关闭" }, + }, + }, + }, + }, + }, + }, + socialLinks: [{ icon: "github", link: repository }], + footer: { + message: "Released under the Apache-2.0 License.", + copyright: "Copyright ReMe contributors", + }, + }, + locales: { + zh: { + label: "简体中文", + lang: "zh-CN", + link: "/zh/", + themeConfig: { + nav: nav("zh"), + sidebar: { "/zh/": sidebar("zh") }, + outline: { label: "页面导航", level: [2, 3] }, + docFooter: { prev: "上一页", next: "下一页" }, + darkModeSwitchLabel: "外观", + sidebarMenuLabel: "菜单", + returnToTopLabel: "返回顶部", + langMenuLabel: "切换语言", + }, + }, + en: { + label: "English", + lang: "en-US", + link: "/en/", + themeConfig: { + nav: nav("en"), + sidebar: { "/en/": sidebar("en") }, + outline: { label: "On this page", level: [2, 3] }, + docFooter: { prev: "Previous page", next: "Next page" }, + }, + }, + }, +}); diff --git a/docs/.vitepress/legacy-routes.mjs b/docs/.vitepress/legacy-routes.mjs new file mode 100644 index 00000000..bdf3607e --- /dev/null +++ b/docs/.vitepress/legacy-routes.mjs @@ -0,0 +1,47 @@ +export const legacyRoutes = { + "readme-zh": "/zh/", + "readme-en": "/en/", + "zh-quick_start": "/zh/quick_start", + "en-quick_start": "/en/quick_start", + "zh-plugin_management": "/zh/plugin_management", + "en-plugin_management": "/en/plugin_management", + "zh-memory_as_file": "/zh/memory_as_file", + "en-memory_as_file": "/en/memory_as_file", + "zh-memory_search": "/zh/memory_search", + "en-memory_search": "/en/memory_search", + "zh-auto_memory": "/zh/auto_memory", + "en-auto_memory": "/en/auto_memory", + "zh-auto_resource": "/zh/auto_resource", + "en-auto_resource": "/en/auto_resource", + "zh-auto_link": "/zh/auto_link", + "en-auto_link": "/en/auto_link", + "zh-auto_dream": "/zh/auto_dream", + "en-auto_dream": "/en/auto_dream", + "zh-proactive": "/zh/proactive", + "en-proactive": "/en/proactive", + "zh-reme_scene": "/zh/reme_scene", + "en-reme_scene": "/en/reme_scene", + "zh-framework": "/zh/framework", + "en-framework": "/en/framework", + "zh-reme-blog": "/zh/reme-blog", + "en-reme-blog": "/en/reme-blog", + "zh-contributing": "/zh/contributing", + "en-contributing": "/en/contributing", + "typescript-zh": "/zh/integrations/typescript", + "typescript-en": "/en/integrations/typescript", + "studio-zh": "/zh/workspace/studio", + "studio-en": "/en/workspace/studio", + "daily-paper-zh": "/zh/plugins/daily-paper", + "daily-paper-en": "/en/plugins/daily-paper", + "auto-fin-zh": "/zh/plugins/auto-fin", + "auto-fin-en": "/en/plugins/auto-fin", + "beam-zh": "/zh/benchmarks/beam", + "beam-en": "/en/benchmarks/beam", + "longmemeval-zh": "/zh/benchmarks/longmemeval", + "longmemeval-en": "/en/benchmarks/longmemeval", + "pibench-zh": "/zh/benchmarks/pibench", + "pibench-en": "/en/benchmarks/pibench", + "toolmemory-zh": "/zh/benchmarks/toolmemory", + "toolmemory-en": "/en/benchmarks/toolmemory", + "agents-guide": "https://github.com/agentscope-ai/ReMe/blob/main/AGENTS.md", +}; diff --git a/docs/.vitepress/theme/CopyMarkdownButton.vue b/docs/.vitepress/theme/CopyMarkdownButton.vue new file mode 100644 index 00000000..b3805638 --- /dev/null +++ b/docs/.vitepress/theme/CopyMarkdownButton.vue @@ -0,0 +1,32 @@ + + + diff --git a/docs/.vitepress/theme/SourceLink.vue b/docs/.vitepress/theme/SourceLink.vue new file mode 100644 index 00000000..cebc353e --- /dev/null +++ b/docs/.vitepress/theme/SourceLink.vue @@ -0,0 +1,15 @@ + + + diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css new file mode 100644 index 00000000..9f966ef5 --- /dev/null +++ b/docs/.vitepress/theme/custom.css @@ -0,0 +1,336 @@ +:root { + --vp-layout-max-width: 1560px; + --vp-c-brand-1: #087f6a; + --vp-c-brand-2: #086554; + --vp-c-brand-3: #19a98f; + --vp-c-brand-soft: rgba(8, 127, 106, 0.14); + --vp-c-bg: #ffffff; + --vp-c-bg-alt: #f4f7f5; + --vp-c-bg-elv: #ffffff; + --vp-c-bg-soft: #f1f6f3; + --vp-c-text-1: #17221d; + --vp-c-text-2: #526159; + --vp-c-text-3: #718078; + --vp-c-divider: #dce5e0; + --vp-font-family-base: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --vp-font-family-mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace; + --reme-blue: #3156d9; + --reme-green: #087f6a; +} + +.dark { + --vp-c-brand-1: #57dfc3; + --vp-c-brand-2: #35c6a9; + --vp-c-brand-3: #087f6a; + --vp-c-brand-soft: rgba(87, 223, 195, 0.14); + --vp-c-bg: #0d1512; + --vp-c-bg-alt: #09100d; + --vp-c-bg-elv: #14201b; + --vp-c-bg-soft: #17251f; + --vp-c-text-1: #edf7f3; + --vp-c-text-2: #bacbc4; + --vp-c-text-3: #91a49c; + --vp-c-divider: #283a33; +} + +body { + background: + radial-gradient(circle at 8% 8%, rgba(25, 201, 176, 0.055), transparent 28rem), + var(--vp-c-bg); +} + +.VPNav { + border-bottom: 1px solid color-mix(in srgb, var(--vp-c-divider) 84%, transparent); + background: color-mix(in srgb, var(--vp-c-bg) 88%, transparent); + backdrop-filter: blur(18px) saturate(140%); +} + +.VPNavBarTitle .logo { + width: 30px; + height: 30px; +} + +.VPNavBarTitle .title { + font-weight: 780; + letter-spacing: -0.02em; +} + +.VPNavBarSearch .DocSearch-Button, +.VPNavBarSearch button { + min-width: 190px; + border: 1px solid var(--vp-c-divider); + border-radius: 10px; + background: var(--vp-c-bg-alt); +} + +.VPSidebar { + border-right: 1px solid var(--vp-c-divider); + background: color-mix(in srgb, var(--vp-c-bg-alt) 82%, transparent); +} + +.VPSidebarItem .text { + font-size: 14px; +} + +.VPSidebarItem.level-0 > .item > .text { + color: var(--vp-c-text-1); + font-weight: 750; +} + +.VPSidebarItem.is-active > .item .link > .text { + color: var(--vp-c-brand-1); +} + +.VPDocAsideOutline { + border-left-color: var(--vp-c-divider); +} + +.VPDoc .container > .content { + min-width: 0; +} + +.VPDoc .content-container { + max-width: 900px !important; +} + +.vp-doc { + color: var(--vp-c-text-1); + font-size: 16px; + line-height: 1.78; +} + +.vp-doc h1 { + margin-bottom: 28px; + font-size: clamp(36px, 5vw, 52px); + line-height: 1.08; + letter-spacing: -0.045em; +} + +.vp-doc h2 { + margin-top: 52px; + border-top-color: var(--vp-c-divider); + font-size: 27px; + letter-spacing: -0.025em; +} + +.vp-doc h3 { + margin-top: 34px; + font-size: 20px; +} + +.vp-doc :not(pre) > code { + border-radius: 5px; + color: color-mix(in srgb, var(--vp-c-brand-1) 82%, var(--vp-c-text-1)); +} + +.vp-doc div[class*="language-"] { + border: 1px solid var(--vp-c-divider); + border-radius: 12px; + box-shadow: inset 3px 0 0 color-mix(in srgb, var(--vp-c-brand-1) 62%, transparent); +} + +.copy-markdown-wrap { + display: flex; + justify-content: flex-end; + margin-bottom: 18px; +} + +.copy-markdown { + display: inline-flex; + align-items: center; + gap: 7px; + min-height: 34px; + padding: 6px 12px; + border: 1px solid var(--vp-c-divider); + border-radius: 9px; + color: var(--vp-c-text-2); + background: var(--vp-c-bg-soft); + cursor: pointer; + font-size: 13px; + font-weight: 650; +} + +.copy-markdown:hover, +.copy-markdown.copied { + border-color: var(--vp-c-brand-1); + color: var(--vp-c-brand-1); +} + +.copy-markdown svg { + width: 15px; + height: 15px; + fill: none; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2; +} + +.source-link-wrap { + margin-top: 48px; + padding-top: 20px; + border-top: 1px solid var(--vp-c-divider); + font-size: 13px; +} + +.source-link-wrap a { + color: var(--vp-c-text-3); + text-decoration: none; +} + +.source-link-wrap a:hover { + color: var(--vp-c-brand-1); +} + +.VPHome { + background: + radial-gradient(circle at 14% 14%, rgba(25, 201, 176, 0.14), transparent 30rem), + radial-gradient(circle at 86% 10%, rgba(49, 86, 217, 0.11), transparent 28rem); +} + +.VPHero .name { + background: linear-gradient(120deg, var(--reme-green), var(--reme-blue)); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.VPHero .name, +.VPHero .text { + letter-spacing: -0.05em; +} + +.VPHero .image-bg { + width: min(88%, 420px); + height: 220px; + border-radius: 42%; + background: linear-gradient(125deg, rgba(25, 201, 176, 0.34), rgba(49, 86, 217, 0.25)); + filter: blur(52px); +} + +.VPHero .image-container { + isolation: isolate; + perspective: 900px; +} + +.VPHero .image-container::before, +.VPHero .image-container::after { + position: absolute; + content: ""; + pointer-events: none; +} + +.VPHero .image-container::before { + z-index: 0; + top: 50%; + left: 50%; + width: min(88%, 430px); + height: 210px; + border: 1px solid color-mix(in srgb, var(--vp-c-bg-elv) 64%, var(--reme-blue)); + border-radius: 30px; + background: + linear-gradient(135deg, color-mix(in srgb, var(--vp-c-bg-elv) 92%, transparent), color-mix(in srgb, var(--vp-c-bg-soft) 76%, transparent)), + radial-gradient(circle at 15% 15%, rgba(25, 201, 176, 0.14), transparent 42%); + box-shadow: + 0 30px 70px rgba(19, 70, 91, 0.16), + inset 0 1px 0 color-mix(in srgb, white 72%, transparent); + backdrop-filter: blur(22px) saturate(135%); + transform: translate(-50%, -50%) rotate(-1.5deg); +} + +.VPHero .image-container::after { + z-index: -1; + top: 50%; + left: 50%; + width: min(78%, 380px); + height: 210px; + border: 1px solid rgba(49, 86, 217, 0.17); + border-radius: 30px; + background: linear-gradient(135deg, rgba(25, 201, 176, 0.1), rgba(49, 86, 217, 0.11)); + transform: translate(-46%, -48%) rotate(7deg); +} + +.VPHero .image-src { + z-index: 1; + width: min(76%, 380px); + max-width: 380px !important; + max-height: 150px !important; + object-fit: contain; + filter: drop-shadow(0 12px 20px rgba(18, 78, 105, 0.16)); +} + +.VPHomeFeatures .item:nth-child(1) { --feature-accent: #18b99e; } +.VPHomeFeatures .item:nth-child(2) { --feature-accent: #25a8dc; } +.VPHomeFeatures .item:nth-child(3) { --feature-accent: #6575e8; } +.VPHomeFeatures .item:nth-child(4) { --feature-accent: #e69a42; } + +.VPHomeFeatures .VPFeature { + border-color: color-mix(in srgb, var(--feature-accent) 28%, var(--vp-c-divider)); + border-radius: 16px; + background: + radial-gradient(circle at 10% 4%, color-mix(in srgb, var(--feature-accent) 16%, transparent), transparent 52%), + linear-gradient(150deg, var(--vp-c-bg-elv), color-mix(in srgb, var(--feature-accent) 7%, var(--vp-c-bg-soft))); + box-shadow: + inset 0 1px 0 color-mix(in srgb, white 76%, transparent), + 0 8px 24px color-mix(in srgb, var(--feature-accent) 7%, transparent); + transition: transform 160ms ease, border-color 160ms ease, box-shadow 160ms ease; +} + +.VPHomeFeatures .VPFeature .box { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + grid-template-rows: auto 1fr; + column-gap: 12px; + align-items: center; +} + +.VPHomeFeatures .VPFeature .icon { + grid-column: 1; + grid-row: 1; + width: auto; + height: auto; + margin: 0; + border: 0; + background: transparent; + box-shadow: none; + font-size: 25px; +} + +.VPHomeFeatures .VPFeature .title { + grid-column: 2; + grid-row: 1; + color: color-mix(in srgb, var(--feature-accent) 22%, var(--vp-c-text-1)); +} + +.VPHomeFeatures .VPFeature .details { + grid-column: 1 / -1; + grid-row: 2; + align-self: start; + padding-top: 18px; +} + +.VPHomeFeatures .VPFeature .link-text { + grid-column: 1 / -1; +} + +.VPHomeFeatures .VPFeature:hover { + transform: translateY(-3px); + border-color: color-mix(in srgb, var(--feature-accent) 48%, var(--vp-c-divider)); + box-shadow: 0 18px 38px color-mix(in srgb, var(--feature-accent) 15%, transparent); +} + +.dark .VPHomeFeatures .VPFeature { + background: + radial-gradient(circle at 10% 4%, color-mix(in srgb, var(--feature-accent) 18%, transparent), transparent 54%), + linear-gradient(150deg, var(--vp-c-bg-elv), color-mix(in srgb, var(--feature-accent) 8%, var(--vp-c-bg-soft))); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06); +} + +@media (max-width: 768px) { + .vp-doc h1 { font-size: 34px; } + .vp-doc h2 { margin-top: 44px; font-size: 24px; } + .copy-markdown-wrap { margin-top: -8px; } + .VPHero .image-container::before, + .VPHero .image-container::after { height: 176px; border-radius: 24px; } + .VPHero .image-src { width: 72%; max-height: 120px !important; } +} diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts new file mode 100644 index 00000000..5edbc9e7 --- /dev/null +++ b/docs/.vitepress/theme/index.ts @@ -0,0 +1,15 @@ +import { h } from "vue"; +import DefaultTheme from "vitepress/theme"; +import CopyMarkdownButton from "./CopyMarkdownButton.vue"; +import SourceLink from "./SourceLink.vue"; +import "./custom.css"; + +export default { + extends: DefaultTheme, + Layout() { + return h(DefaultTheme.Layout, null, { + "doc-before": () => h(CopyMarkdownButton), + "doc-after": () => h(SourceLink), + }); + }, +}; diff --git a/docs/en/configuration.md b/docs/en/configuration.md new file mode 100644 index 00000000..49e84bc1 --- /dev/null +++ b/docs/en/configuration.md @@ -0,0 +1,157 @@ +--- +title: Configuration +description: ReMe configuration files, environment expansion, command-line overrides, and core components. +--- + +# Configuration + +ReMe uses YAML or JSON to describe its Service, Jobs, and Components. The built-in default is `reme/config/default.yaml`. Select another configuration at startup and apply command-line overrides when needed. + +## Precedence + +Configuration is merged in this order, with later values winning: + +1. `application_defaults` from enabled plugins. +2. The selected file; `default` is used when none is specified. +3. CLI dot-notation overrides. + +```bash +reme start +reme start config=demo +reme start config=/absolute/path/to/app.yaml +reme start service.port=8181 workspace_dir=/data/reme +``` + +`config` accepts a built-in name or a `.yaml`, `.yml`, or `.json` file. Overrides are deep-merged, so changing `service.port` preserves sibling service settings. + +## CLI values + +Arguments use `key=value`; leading `-` or `--` is accepted: + +```bash +reme start --service.port=8181 --service.web_enabled=false +``` + +Values support null, booleans, numbers, JSON arrays and objects, quoted JSON strings, and plain strings. Numeric-looking values with leading zeroes, such as `007`, remain strings. Quote values such as `"true"` in JSON when they must remain strings. + +## Environment variables + +Configuration recursively expands: + +```yaml +api_key: ${LLM_API_KEY} +base_url: ${LLM_BASE_URL:-https://example.com/v1} +``` + +`${VAR}` fails when undefined; `${VAR:-default}` uses its fallback. ReMe also searches for `.env` from the command's working directory through at most five parents. + +Keep secrets in `.env` or the process environment, never in committed configuration. + +## Application fields + +| Field | Default | Purpose | +|---|---|---| +| `app_name` | `ReMe` | Display name | +| `workspace_dir` | `.reme` | User-owned workspace root, normalized to an absolute path | +| `metadata_dir` | `metadata` | Rebuildable indexes, graphs, and catalogs | +| `session_dir` | `session` | Agent sessions; standard transcripts use `session/dialog` | +| `mem_session_dir` | `mem_session` | Agent-wrapper sessions and configuration | +| `resource_dir` | `resource` | External resources | +| `daily_dir` | `daily` | Daily memory | +| `digest_dir` | `digest` | Consolidated long-term memory | +| `timezone` | `Asia/Shanghai` | IANA timezone used for dates and cron jobs | +| `language` | empty | Default language for LLM interactions | +| `plugins` | `[]` | Installed plugins enabled for this Application | +| `service` | HTTP | Service configuration | +| `jobs` | default Jobs | Job configurations by name | +| `components` | defaults | Components grouped by type and name | + +`session_dir` must remain workspace-relative. + +## LLM + +The default LLM uses an OpenAI-compatible interface: + +```yaml +components: + as_llm: + default: + backend: openai + model: qwen3.7-plus + context_size: 200000 + credential: + api_key: ${LLM_API_KEY:-} + base_url: ${LLM_BASE_URL:-} +``` + +Built-in registrations include `openai`, `anthropic`, `dashscope`, `deepseek`, `gemini`, `moonshot`, `ollama`, and `xai`. Their detailed model fields follow the corresponding AgentScope wrappers. + +File operations, BM25 search, and wikilink traversal do not require an LLM. Evolution workflows such as `auto_memory`, `auto_resource`, and `auto_dream` do. + +## Embeddings + +Vector retrieval is disabled by default. Credentials alone do not enable it: configure `as_embedding`, `embedding_store`, and connect the store to `file_store`. + +```yaml +components: + as_embedding: + default: + backend: openai + model: text-embedding-v4 + dimensions: 1024 + credential: + api_key: ${EMBEDDING_API_KEY} + base_url: ${EMBEDDING_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1} + embedding_store: + default: + backend: local + as_embedding: default + file_store: + default: + backend: local + embedding_store: default + keyword_index: default + file_graph: default +``` + +Rebuild the embedding index after changing the model or dimensions. + +## Service and Jobs + +Minimal HTTP configuration: + +```yaml +service: + backend: http + host: 127.0.0.1 + port: 2333 + web_enabled: true + mcp_enabled: true + mcp_path: /mcp +``` + +A Job declares a backend, parameter schema, and ordered Steps: + +```yaml +jobs: + example: + backend: base + description: Example job + parameters: + type: object + properties: + text: { type: string } + required: [text] + steps: + - backend: example_step +``` + +Set `enable_serve: false` to keep a Job internal. Background and cron Jobs are never service-exposed. + +## Inspect the effective configuration + +```bash +reme app_config +``` + +The result is the merged, validated configuration with secrets redacted. Use it when diagnosing plugin or override precedence. The authoritative contracts remain `reme/schema/application_config.py` and `reme/config/default.yaml`. diff --git a/docs/en/contributing.md b/docs/en/contributing.md index ad962169..99c9fac1 100644 --- a/docs/en/contributing.md +++ b/docs/en/contributing.md @@ -209,6 +209,13 @@ Documentation lives under: docs/ ``` +User guides should have matching `docs/zh/` and `docs/en/` versions and appear in the corresponding navigation in +`docs/.vitepress/config.mts`. The ReMe Studio, TypeScript, plugin, and benchmark READMEs remain canonical in their own +directories; `github-pages/scripts/generate-content.mjs` mirrors them during builds. Never edit `.generated/` or `dist/`. + +The Job API reference is generated from `reme/config/default.yaml`. Update that YAML and its tests when a default Job +contract changes rather than editing generated pages. + Documentation should: - Use clear titles that directly identify a capability or flow. @@ -216,6 +223,15 @@ Documentation should: - Use real repository paths such as `reme/config/default.yaml`, `reme/steps/`, and `tests/unit/`. - Describe default behavior according to the current code, `pyproject.toml`, and default configuration. +Validate the documentation site with: + +```bash +cd github-pages +npm ci +npm test +npm run build +``` + --- ## Getting Help diff --git a/docs/en/faq.md b/docs/en/faq.md new file mode 100644 index 00000000..6aeb113a --- /dev/null +++ b/docs/en/faq.md @@ -0,0 +1,71 @@ +--- +title: Frequently Asked Questions +description: Quick answers for ReMe installation, services, models, retrieval, files, and plugins. +--- + +# Frequently Asked Questions + +## Do basic file operations require a model API key? + +No. `write`, `read`, `list`, `stat`, BM25 search, and wikilink traversal work without model credentials. `auto_memory`, `auto_resource`, and `auto_dream` require an LLM. + +## Why is search still BM25-only after setting an embedding key? + +Embeddings are disabled by default. Configure `as_embedding` and `embedding_store`, then connect `file_store.default.embedding_store` to that component. See [Configuration](./configuration.md#embeddings). + +## Why did `reme reindex` not discover a new file? + +`reindex` rebuilds indexes from current `file_chunks`; it does not scan the workspace. Check `index_update_loop`, the watched directory and extension, and `health_check`. + +## How do I use another workspace? + +```bash +reme start workspace_dir=/absolute/path/to/memory +``` + +Ordinary CLI calls discover the running service, so they do not need the workspace argument again. + +## What if port 2333 is occupied? + +Do not stop an unknown listener. Select another port: + +```bash +reme start service.port=8181 +``` + +Then confirm it with `reme find_reme`. + +## Why is an installed plugin missing its Jobs? + +Installation only makes the distribution discoverable in the active Python environment. Enable it for the Application: + +```bash +reme start plugins='["auto-fin"]' +``` + +Restart a running service after changing package or enablement state. + +## May I edit workspace Markdown directly? + +Yes. Files are the source of truth and watchers ingest changes. Keep frontmatter valid, use complete workspace-relative wikilinks, and avoid unconditional concurrent saves. + +## May I expose ReMe publicly? + +Not with the default configuration alone. Jobs can write and delete, HTTP CORS is permissive, and there is no general authentication layer. Use a controlled network or authenticated TLS reverse proxy and restrict `service.jobs`. + +## How should I back up and migrate memory? + +Stop writes and back up the complete workspace. `session/`, `resource/`, `daily/`, and `digest/` are the key sources; `metadata/` can be backed up or rebuilt. See [Diagnostics, Backup, and Recovery](./operations.md). + +## Why is Studio unavailable? + +The base `reme-ai` package has no frontend assets. Install `reme-ai[web]` or `reme-ai[core]`, or set `service.web_static_dir`. Missing Studio assets do not disable the Job API. + +## Which capabilities does the running service expose? + +```bash +reme help +reme app_config +``` + +Static documentation describes defaults; plugins and custom configuration may change the active service. diff --git a/docs/en/index.md b/docs/en/index.md new file mode 100644 index 00000000..c4048ca8 --- /dev/null +++ b/docs/en/index.md @@ -0,0 +1,36 @@ +--- +layout: home +title: ReMe Documentation +titleTemplate: false +hero: + name: ReMe + text: Memory that works for agents + tagline: Files remain yours. ReMe turns conversations and resources into readable, editable, searchable, interconnected local files. + image: + src: /reme-logo.svg + alt: ReMe logo + actions: + - theme: brand + text: Quick Start + link: /en/quick_start + - theme: alt + text: Configuration + link: /en/configuration +features: + - icon: 📁 + title: Memory as files + details: Workspace files are the durable source of truth; indexes, graphs, and caches remain rebuildable. + link: /en/memory_as_file + - icon: 🧠 + title: Memory workflows + details: Turn conversations and resources into daily notes, then consolidate them into connected long-term digests. + link: /en/auto_memory + - icon: 🔎 + title: Search and graph + details: Combine keyword and optional vector retrieval with wikilink graph expansion. + link: /en/memory_search + - icon: 🔌 + title: Agent integrations + details: Connect existing agents through the CLI, HTTP, MCP, and host adapters. + link: /en/integrations +--- diff --git a/docs/en/integrations.md b/docs/en/integrations.md new file mode 100644 index 00000000..1e9bddc0 --- /dev/null +++ b/docs/en/integrations.md @@ -0,0 +1,63 @@ +--- +title: Agent Integrations +description: Connect ReMe to agents through the CLI, HTTP, MCP, Skills, and host adapters. +--- + +# Agent Integrations + +ReMe keeps memory in an independent service and a user-owned workspace. Multiple agents can call the same memory system without binding storage to one model or host. + +## Choose an interface + +| Scenario | Recommended interface | +|---|---| +| Local script or hook | ReMe CLI | +| Application backend | HTTP Client | +| Tool-protocol host | MCP | +| TypeScript agent | `@agentscope-ai/reme` | +| Claude Code | MCP + Skill + Stop Hook | +| Hermes Agent | Memory provider adapter | +| Codex or another coding agent | `reme_memory` Skill or MCP | + +## General memory loop + +1. Before answering, call `search` for relevant memory. +2. Use `read` on high-value results and `traverse` when relationships matter. +3. Retain workspace-relative source paths in the answer. +4. At session end, pass source messages to `auto_memory`. +5. Let background or scheduled workflows consolidate daily notes into digest memory. + +An empty search result must remain empty; do not present model inference as recalled history. + +## MCP + +The default HTTP service exposes streamable HTTP MCP at `http://127.0.0.1:2333/mcp`. Common tools include `search`, `read`, `traverse`, `list`, `auto_memory`, and `proactive`. + +Use `service.jobs` to expose a read-only subset or keep write tools in a separate configuration. + +## CLI and Skill + +`skills/reme_memory/SKILL.md` defines a general workflow for agents that can run local commands: installation checks, service discovery, retrieval, reading, and persistence boundaries. + +It deliberately avoids silently modifying Python environments, stopping unknown processes on port conflicts, writing recalled tool output back as conversation source, or persisting credentials. + +## TypeScript, OpenClaw, and DeepSeek Harness + +The [`@agentscope-ai/reme` TypeScript package](./integrations/typescript.md) provides the shared HTTP client and host adapters. See the dedicated guides for [DeepSeek Harness](./integrations/dsh.md) and [OpenClaw](./integrations/openclaw.md). + +## Claude Code + +`integrations/claude_code/` provides streamable HTTP MCP configuration, a `reme-memory` Skill, and a Stop hook that calls `auto_memory_cc`. Follow that directory's README for installation. + +## Hermes Agent + +`integrations/hermes_agent/` provides a memory provider that recalls context before model calls and asynchronously invokes `auto_memory` after each turn. + +## Production guidance + +- choose a stable absolute `workspace_dir`; +- reuse a service discovered by `reme find_reme`; +- treat `reme help` as the active Job contract; +- apply timeouts and failure logging to writes; +- do not block the host's core response path when memory is temporarily unavailable; +- use authentication, TLS, and a minimal Job allowlist for remote access. diff --git a/docs/en/operations.md b/docs/en/operations.md new file mode 100644 index 00000000..50160661 --- /dev/null +++ b/docs/en/operations.md @@ -0,0 +1,102 @@ +--- +title: Diagnostics, Backup, and Recovery +description: ReMe health checks, logs, index maintenance, workspace backup, migration, and recovery. +--- + +# Diagnostics, Backup, and Recovery + +ReMe recovery protects user-owned workspace files and rebuilds catalogs, indexes, and graphs from those sources. Never delete or rewrite user memory merely to repair derived state. + +## Quick diagnosis + +Run these in order: + +```bash +reme find_reme +reme version +reme health_check +reme status +reme app_config +``` + +- `find_reme` confirms the actual host, port, and PID; +- `version` verifies that the CLI reaches the service; +- `health_check` reports component health; +- `status` estimates stateful component memory and process RSS; +- `app_config` returns the effective configuration with secrets redacted. + +## Logs and common symptoms + +`log_to_console` and `log_to_file` control logging. For startup failures, inspect the first exception rather than later client connection errors. + +| Symptom | Check first | +|---|---| +| CLI cannot find ReMe | `reme find_reme`, process state, startup directory, and port | +| Automatic memory fails | LLM backend, model, API key, and base URL | +| Search is BM25-only | Whether an embedding store is connected to `file_store` | +| New files are absent | Directory, extension, watcher, and `health_check` | +| Studio fails but API works | Installed web extra, static path, and browser console | +| Installed plugin is unavailable | Python interpreter, `plugins` configuration, and service restart | + +## Index maintenance + +```bash +reme reindex scope=all +reme reindex scope=bm25 +reme reindex scope=embedding +``` + +`reindex` rebuilds BM25 and/or embedding indexes from the current `file_chunks`. It does not scan the workspace, rechunk files, or rebuild the wikilink graph. Diagnose the watcher first when ingestion is the problem. + +Rebuild a daily index page separately: + +```bash +reme daily_reindex date=2026-09-04 +``` + +## Backup + +Stop writes or stop the service, then back up the complete workspace. The most important sources are: + +- `session/` for conversation sources; +- `resource/` for external resources; +- `daily/` for daily memory; +- `digest/` for consolidated memory. + +`metadata/` contains indexes, graphs, and catalogs. Backing it up accelerates restoration, but it is not the sole source of truth. + +Use an explicit, stable absolute `workspace_dir` for durable deployments rather than relying on an incidental `.reme/` under the current directory. + +## Migrate a workspace + +1. Stop the old service to prevent writes during the copy. +2. Copy the complete workspace while preserving timestamps. +3. Start with the new absolute path: + +```bash +reme start workspace_dir=/new/location/reme-memory +``` + +4. Run `health_check`, `status`, and a representative `search`. +5. Rebuild embeddings if their model or dimensions changed. + +Do not push a workspace containing private conversations to a public repository. + +## Recover derived state + +Do not remove anything until a backup exists. Then: + +1. preserve `session/`, `resource/`, `daily/`, and `digest/`; +2. record the effective configuration and component backends; +3. verify that the failure is limited to `metadata/`; +4. move suspect derived state to an isolated backup location; +5. restart with the same configuration and let watchers rebuild; +6. validate search, graph traversal, and daily indexes. + +The internal layout of metadata files is not a public automation contract. + +## Concurrent editing + +When Studio or an editor saves a complete file, pass the mtime from `stat` as `save.expected_mtime`. A save then fails if another actor changed the file after it was opened, avoiding silent overwrites. + +File Jobs enforce workspace containment and per-path locking. Do not bypass them to write arbitrary absolute paths. diff --git a/docs/en/plugin_development.md b/docs/en/plugin_development.md new file mode 100644 index 00000000..074033c3 --- /dev/null +++ b/docs/en/plugin_development.md @@ -0,0 +1,101 @@ +--- +title: Plugin Development +description: Create, register, configure, test, and publish a ReMe plugin. +--- + +# Plugin Development + +A ReMe plugin is a regular Python distribution exposed through the `reme.plugins` entry-point group. Its package-level `plugin.yaml` can register Step and Component backends and provide default Application configuration. + +## Minimal structure + +```text +my-plugin/ +├── pyproject.toml +└── src/my_plugin/ + ├── __init__.py + ├── plugin.yaml + └── steps.py +``` + +`pyproject.toml`: + +```toml +[project.entry-points."reme.plugins"] +my-plugin = "my_plugin" +``` + +`plugin.yaml`: + +```yaml +name: my-plugin +backends: + my_step: my_plugin.steps:MyStep +application_defaults: + jobs: + my_action: + backend: base + description: Run my plugin action + parameters: + type: object + properties: + text: { type: string } + required: [text] + steps: + - backend: my_step +``` + +## Implement a Step + +```python +from reme.components.component_registry import R +from reme.steps.base_step import BaseStep + + +@R.register("my_step") +class MyStep(BaseStep): + async def execute(self): + self.context.response.answer = self.context.data["text"] +``` + +Step instances belong to one Job invocation. Put shared in-memory state under a narrow `app_context.metadata` key. Promote state that needs lifecycle, locking, or persistence to a Component or workspace file. + +## Configuration merge + +`application_defaults` is a partial `ApplicationConfig`: + +```text +plugin defaults < selected/default config < CLI overrides +``` + +Plugins must not rewrite user configuration. Their backends enter an Application-local registry only when the plugin appears in that Application's `plugins` list. + +## Local validation + +```bash +reme plugins validate ./path/to/my-plugin +reme plugins install ./path/to/my-plugin --editable +reme plugins list +reme plugins show my-plugin +reme start plugins='["my-plugin"]' +reme my_action text=hello +``` + +Validation imports plugin code, so run it only for trusted sources. + +## Test boundaries + +- create workspaces with `tmp_path`; +- mock network, model, and subprocess boundaries; +- verify disabled plugins do not mutate the built-in registry; +- verify plugin defaults and explicit configuration precedence; +- keep tasks, clients, and executors under Component lifecycle; +- never delete or rewrite user source files to repair derived state. + +The repository's Daily Paper, Auto Fin, LME, and BEAM plugins are complete examples. + +## Compatibility + +Legacy Python Plugin descriptors and the `reme.configs` entry point remain supported during migration, but new plugins should use `plugin.yaml`. Enablement always belongs to an Application rather than a process-global switch. + +See [Plugin Management](./plugin_management.md) for installation, upgrades, and removal. diff --git a/docs/en/plugin_management.md b/docs/en/plugin_management.md index 31b8400a..a2429787 100644 --- a/docs/en/plugin_management.md +++ b/docs/en/plugin_management.md @@ -176,6 +176,16 @@ When the application uses an MCP service, service-enabled plugin Jobs appear as Custom application configs must provide the plugin's runtime dependencies, including an `agent_wrapper.default` and the `search` and `read` Jobs used by Auto Fin. +## Benchmark application presets + +The [LME](../../plugins/lme/README.md) and [BEAM](../../plugins/beam/README.md) plugins +register their backends and plugin-owned Jobs in `plugin.yaml`. ReMe's built-in `benchmark` +preset provides the shared core Jobs and components without inheriting `default`, so default +background and cron jobs are not included. Install the selected benchmark plugin, then use +`config=benchmark` together with `plugins=["lme"]` or `plugins=["beam"]`. The repository's +benchmark runners enable the corresponding installed plugin automatically; editable installation +keeps local plugin changes visible. Dataset runners remain under `benchmark/`. + ## Uninstall a plugin Use the plugin entry-point name, not necessarily the distribution name: diff --git a/docs/en/quick_start.md b/docs/en/quick_start.md index 01b7714d..2b317245 100644 --- a/docs/en/quick_start.md +++ b/docs/en/quick_start.md @@ -1,5 +1,12 @@ +--- +title: Quick Start +description: Install and start ReMe, then complete a first file, retrieval, and automatic-memory workflow. +--- + # Quick Start +This page gets one working loop running. See [Configuration](./configuration.md) for the full configuration contract and [Services and Deployment](./services.md) for HTTP or MCP integration. + ## Installation ReMe requires Python 3.11+. @@ -237,3 +244,5 @@ You can also specify a YAML or JSON configuration file: ```bash reme start config=/path/to/custom.yaml ``` + +Continue with the [CLI Reference](./reference/cli.md), [Job API Reference](./reference/jobs.md), or [Diagnostics, Backup, and Recovery](./operations.md). diff --git a/docs/en/reference/cli.md b/docs/en/reference/cli.md new file mode 100644 index 00000000..5a3c372d --- /dev/null +++ b/docs/en/reference/cli.md @@ -0,0 +1,87 @@ +--- +title: CLI Reference +description: ReMe command syntax, service invocation, configuration overrides, and plugin commands. +--- + +# CLI Reference + +The basic syntax is: + +```text +reme ACTION key=value ... +``` + +## Start an Application + +```bash +reme start +reme start config=demo +reme start workspace_dir=/data/reme service.port=8181 +reme start job=search query="keywords" limit=5 +``` + +`start job=` runs one Job through a one-shot service; plain `start` runs the configured Service. + +## Call Jobs + +Once a service is running, each action name is a Job name: + +```bash +reme help +reme health_check +reme search query="project decision" limit=10 +reme read path=digest/wiki/project.md start_line=1 end_line=80 +``` + +Use JSON for structured values: + +```bash +reme auto_memory \ + session_id=example \ + messages='[{"role":"user","content":"Remember this preference"}]' +``` + +Client-selection arguments—`backend`, `transport`, `host`, `port`, `timeout`, `command`, `args`, and `show_metadata`—configure the client and never leak into the Job payload. + +## Configuration overrides + +```bash +reme start \ + config=/path/to/custom.yaml \ + service.port=8181 \ + service.web_enabled=false \ + plugins='["auto-fin"]' +``` + +Leading `-` or `--` is optional. Use dots for nested keys and JSON for arrays and objects. + +## Service discovery + +```bash +reme find_reme +``` + +This reports a discovered service but never starts or replaces a process. + +## Plugin commands + +Package management runs locally rather than through HTTP or MCP: + +```bash +reme plugins list +reme plugins show auto-fin +reme plugins validate auto-fin +reme plugins install reme-auto-fin +reme plugins uninstall auto-fin +``` + +See [Plugin Management](../plugin_management.md) for the complete workflow. + +## Discover active capabilities + +The [Job API Reference](./jobs.md) describes the default configuration. Plugins and custom YAML may change the running service, so automation should prefer: + +```bash +reme help +reme app_config +``` diff --git a/docs/en/services.md b/docs/en/services.md new file mode 100644 index 00000000..c0be01a0 --- /dev/null +++ b/docs/en/services.md @@ -0,0 +1,115 @@ +--- +title: Services and Deployment +description: Run ReMe through HTTP, SSE, MCP, the CLI, and ReMe Studio while respecting its local security boundary. +--- + +# Services and Deployment + +ReMe can run as a local HTTP service, a standalone MCP server, or a one-shot CLI Job. The default starts HTTP on `127.0.0.1:2333` and serves JSON, SSE, streamable HTTP MCP, and optional ReMe Studio from one process. + +## HTTP API + +```bash +reme start +reme start service.host=127.0.0.1 service.port=8181 +``` + +Regular Jobs become `POST /`: + +```bash +curl -s http://127.0.0.1:2333/search \ + -H 'Content-Type: application/json' \ + -d '{"query":"user preferences","limit":5}' +``` + +Job arguments live at the request body's top level. A regular response follows the `Response` schema: + +```json +{"success": true, "answer": "...", "metadata": {}} +``` + +Unhandled Step failures become unsuccessful responses. + +## Streaming and SSE + +Jobs with `backend: stream` also use `POST /`, returning `text/event-stream`. Error paths emit an error chunk and always terminate the stream. The default `chat` Job is streaming: + +```bash +curl -N http://127.0.0.1:2333/chat \ + -H 'Content-Type: application/json' \ + -d '{"query":"Summarize my long-term preferences"}' +``` + +MCP does not expose Stream Jobs. + +## MCP + +The default HTTP service mounts streamable HTTP MCP at `/mcp`: + +```yaml +service: + backend: http + mcp_enabled: true + mcp_path: /mcp + mcp_stateless_http: false +``` + +For a standalone MCP service: + +```bash +reme start service.backend=mcp service.transport=stdio +reme start service.backend=mcp service.transport=sse service.port=2333 +reme start service.backend=mcp service.transport=streamable-http service.port=2333 +``` + +MCP tools come from non-stream Jobs with `enable_serve: true`. Use `service.jobs` as an allowlist. `injected_job_kwargs` adds server-managed values that callers cannot override. + +```yaml +service: + backend: http + jobs: [search, read, traverse, auto_memory] + injected_job_kwargs: + tenant_id: local-user + tool_error_on_failure: true +``` + +## ReMe Studio + +After installing `reme-ai[web]` or `reme-ai[core]`, the default HTTP origin also serves Studio: + +```text +http://127.0.0.1:2333/ +``` + +Disable it with `service.web_enabled=false` or select a build with `service.web_static_dir`. Missing static assets do not prevent the Job API from starting. + +## One-shot Jobs + +```bash +reme start job=search query="user preferences" limit=5 +``` + +This selects the one-shot CLI Service while retaining the normal Application, Component, and Job lifecycle. + +## Service discovery + +```bash +reme find_reme +``` + +Ordinary `reme ` commands prefer the running service's actual backend, host, port, and transport. They fall back to local configuration only when no service is discovered. + +## Security boundary + +ReMe is local-first: + +- the default binds to `127.0.0.1`; +- HTTP CORS allows any origin; +- Jobs may write, move, or delete files; +- the service layer has no general-purpose user authentication. + +Do not expose the default service directly to the public internet. For remote access, place it on a controlled network or behind an authenticated TLS reverse proxy, apply access controls and request-size limits, and expose only necessary Jobs. + +## OpenAPI + +FastAPI exposes the active endpoints through `/docs`, `/redoc`, and `/openapi.json`. The Studio SPA fallback preserves these reserved paths. diff --git a/docs/figure/reme-icon.svg b/docs/figure/reme-icon.svg new file mode 100644 index 00000000..bbb9e6c8 --- /dev/null +++ b/docs/figure/reme-icon.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/figure/reme-logo-fashion.svg b/docs/figure/reme-logo-fashion.svg new file mode 100644 index 00000000..6fe1c868 --- /dev/null +++ b/docs/figure/reme-logo-fashion.svg @@ -0,0 +1,46 @@ + + ReMe + ReMe gradient wordmark and open memory ribbon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ReMe + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..a34cf9c2 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,43 @@ +--- +layout: home +title: ReMe Documentation +titleTemplate: false +head: + - - meta + - http-equiv: refresh + content: "0; url=/zh/" + - - link + - rel: canonical + href: https://reme.agentscope.io/zh/ +hero: + name: ReMe + text: 让 Agent 真正记住 + tagline: 文件属于你,记忆服务于 Agent。ReMe 将对话和资料沉淀为可读、可编辑、可检索、相互链接的本地文件。 + image: + src: /reme-logo.svg + alt: ReMe Logo + actions: + - theme: brand + text: 快速开始 + link: /zh/quick_start + - theme: alt + text: 核心概念 + link: /zh/memory_as_file +features: + - icon: 📁 + title: Local-first + details: Workspace 文件是持久事实源;索引、图谱和缓存都可以重新构建。 + link: /zh/memory_as_file + - icon: 🧠 + title: Memory workflows + details: 将对话和资料写入 daily,自动整理为互相关联的长期 digest。 + link: /zh/auto_memory + - icon: 🔎 + title: Search and graph + details: 结合关键词、可选向量检索与 wikilink 图谱,渐进式展开上下文。 + link: /zh/memory_search + - icon: 🔌 + title: Agent integrations + details: 通过 CLI、HTTP、MCP 和宿主适配器接入已有 Agent。 + link: /zh/integrations +--- diff --git a/docs/zh/configuration.md b/docs/zh/configuration.md new file mode 100644 index 00000000..6fdeb191 --- /dev/null +++ b/docs/zh/configuration.md @@ -0,0 +1,168 @@ +--- +title: 基础配置 +description: ReMe 配置文件、环境变量、命令行覆盖和核心组件配置。 +--- + +# 基础配置 + +ReMe 使用 YAML 或 JSON 描述 Service、Job 和 Component。默认配置位于 `reme/config/default.yaml`;启动时可以选择其他配置,再用命令行覆盖其中的字段。 + +## 配置优先级 + +配置按下面的顺序合并,靠后的值优先: + +1. 已启用插件提供的 `application_defaults`。 +2. 选中的配置文件;未指定时使用内置 `default`。 +3. 命令行 dot notation 覆盖。 + +```bash +reme start +reme start config=demo +reme start config=/absolute/path/to/app.yaml +reme start service.port=8181 workspace_dir=/data/reme +``` + +`config` 支持内置配置名以及 `.yaml`、`.yml`、`.json` 文件。覆盖采用深度合并,不会因为修改 `service.port` 而丢失 `service` 下的其他字段。 + +## 值的解析 + +CLI 参数使用 `key=value`,前导 `-` 或 `--` 也会被接受: + +```bash +reme start --service.port=8181 --service.web_enabled=false +``` + +值支持: + +- `null`、布尔值、整数和浮点数; +- JSON 数组和对象; +- JSON 引号字符串; +- 普通字符串。 + +类似 `007` 的前导零字符串不会被转换成数字。需要保留 `true`、`false` 等字面字符串时,使用 JSON 引号:`value='"true"'`。 + +## 环境变量 + +配置文件会递归展开两种表达式: + +```yaml +api_key: ${LLM_API_KEY} +base_url: ${LLM_BASE_URL:-https://example.com/v1} +``` + +`${VAR}` 在变量未定义时会报错;`${VAR:-default}` 使用默认值。ReMe 还会从命令启动目录向上查找 `.env`,最多检查五级父目录。 + +不要把密钥提交到配置文件或 Git。推荐把密钥放在 `.env` 或进程环境中。 + +## Application 字段 + +| 字段 | 默认值 | 作用 | +|---|---|---| +| `app_name` | `ReMe` | 应用显示名称 | +| `workspace_dir` | `.reme` | 用户拥有的 workspace 根目录,会规范化为绝对路径 | +| `metadata_dir` | `metadata` | 索引、图谱和 catalog 等派生状态 | +| `session_dir` | `session` | Agent 对话记录;标准 transcript 位于 `session/dialog` | +| `mem_session_dir` | `mem_session` | Agent wrapper 的会话和配置 | +| `resource_dir` | `resource` | 外部资料 | +| `daily_dir` | `daily` | Daily memory | +| `digest_dir` | `digest` | 长期整理后的记忆 | +| `timezone` | `Asia/Shanghai` | Cron、日期和梦境流程使用的 IANA 时区 | +| `language` | 空 | LLM 交互默认语言 | +| `plugins` | `[]` | 为当前 Application 启用的已安装插件 | +| `service` | HTTP | 服务端配置 | +| `jobs` | 默认 Job | Job 名到 Job 配置的映射 | +| `components` | 默认组件 | 按类型和名称组织的组件配置 | + +`session_dir` 必须保持 workspace-relative。其他 workspace 子目录也应使用清晰、稳定的相对名称。 + +## LLM 配置 + +默认 LLM 使用 OpenAI-compatible 接口: + +```yaml +components: + as_llm: + default: + backend: openai + model: qwen3.7-plus + context_size: 200000 + credential: + api_key: ${LLM_API_KEY:-} + base_url: ${LLM_BASE_URL:-} +``` + +可注册的内置 backend 包括 `openai`、`anthropic`、`dashscope`、`deepseek`、`gemini`、`moonshot`、`ollama` 和 `xai`。实际字段由对应 AgentScope model wrapper 决定。 + +基础文件操作、BM25 检索、wikilink 遍历不需要 LLM。`auto_memory`、`auto_resource`、`auto_dream` 等演化流程需要可用 LLM。 + +## Embedding 配置 + +向量检索默认关闭。只设置 `EMBEDDING_API_KEY` 不会自动启用它;还需要同时启用 `as_embedding`、`embedding_store`,并把它连接到 `file_store`: + +```yaml +components: + as_embedding: + default: + backend: openai + model: text-embedding-v4 + dimensions: 1024 + credential: + api_key: ${EMBEDDING_API_KEY} + base_url: ${EMBEDDING_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1} + embedding_store: + default: + backend: local + as_embedding: default + file_store: + default: + backend: local + embedding_store: default + keyword_index: default + file_graph: default +``` + +修改 embedding 模型或维度后,应重新构建 embedding 索引。 + +## Service 和 Job + +最小 HTTP 配置: + +```yaml +service: + backend: http + host: 127.0.0.1 + port: 2333 + web_enabled: true + mcp_enabled: true + mcp_path: /mcp +``` + +Job 由 backend、参数 schema 和顺序执行的 Step 组成: + +```yaml +jobs: + example: + backend: base + description: Example job + parameters: + type: object + properties: + text: { type: string } + required: [text] + steps: + - backend: example_step +``` + +设置 `enable_serve: false` 可以保留内部 Job、禁止 Service 暴露。后台和 Cron Job始终不会作为请求端点暴露。 + +## 查看生效配置 + +服务启动后运行: + +```bash +reme app_config +``` + +返回的是已合并、已校验并隐藏密钥后的配置。排查覆盖顺序或插件配置时,应以它为准,而不是只查看某一个 YAML 文件。 + +完整字段定义以 `reme/schema/application_config.py` 和 `reme/config/default.yaml` 为准。 diff --git a/docs/zh/contributing.md b/docs/zh/contributing.md index 2b5c65e0..e2cdbdfc 100644 --- a/docs/zh/contributing.md +++ b/docs/zh/contributing.md @@ -190,6 +190,12 @@ pytest tests/unit/test_reme_cli.py docs/ ``` +用户指南应同时提供 `docs/zh/` 与 `docs/en/` 版本,并在 `docs/.vitepress/config.mts` 的对应导航中注册。ReMe Studio、 +TypeScript、插件和评测的 README 是各自目录中的规范源文件;`github-pages/scripts/generate-content.mjs` 会在构建时镜像它们, +不要编辑 `.generated/` 或 `dist/`。 + +`Job API 参考`由 `reme/config/default.yaml` 自动生成。修改默认 Job 参数时更新 YAML 和测试,不要手工维护生成页。 + 建议文档保持: - 标题明确,直接说明能力或流程。 @@ -197,6 +203,15 @@ docs/ - 涉及路径时使用仓库内真实路径,例如 `reme/config/default.yaml`、`reme/steps/`、`tests/unit/`。 - 涉及默认行为时,以当前代码和 `pyproject.toml`、默认配置为准。 +文档站检查: + +```bash +cd github-pages +npm ci +npm test +npm run build +``` + --- ## 获取帮助 diff --git a/docs/zh/faq.md b/docs/zh/faq.md new file mode 100644 index 00000000..563a93bc --- /dev/null +++ b/docs/zh/faq.md @@ -0,0 +1,75 @@ +--- +title: 常见问题 +description: ReMe 安装、服务、模型、检索、文件和插件问题的快速答案。 +--- + +# 常见问题 + +## 基础文件操作需要模型 API Key 吗? + +不需要。`write`、`read`、`list`、`stat`、BM25 搜索和 wikilink 遍历可以在没有模型凭据时运行。`auto_memory`、`auto_resource` 和 `auto_dream` 需要 LLM。 + +## 为什么配置了 Embedding Key 仍然只有 BM25? + +Embedding 默认未启用。除了凭据,还必须配置 `as_embedding`、`embedding_store`,并让 `file_store.default.embedding_store` 指向该组件。参见[基础配置](./configuration.md#embedding-配置)。 + +## 为什么 `reme reindex` 没有发现新文件? + +`reindex` 只从当前 `file_chunks` 重建 BM25 或 embedding 索引,不扫描 workspace。检查 `index_update_loop` watcher、文件目录、后缀和 `health_check`。 + +## 怎样使用另一个 workspace? + +启动时传入稳定的绝对路径: + +```bash +reme start workspace_dir=/absolute/path/to/memory +``` + +普通 CLI 调用会发现运行服务,不必重复 workspace 参数。 + +## 端口 2333 被占用怎么办? + +不要停止未知监听者。选择另一个端口: + +```bash +reme start service.port=8181 +``` + +然后用 `reme find_reme` 确认发现结果。 + +## 为什么插件安装后仍然没有对应 Job? + +安装只让 distribution 在当前 Python 环境中可见。还需要在应用配置中启用: + +```bash +reme start plugins='["auto-fin"]' +``` + +修改安装或启用状态后需要重启已运行的服务。 + +## 可以直接编辑 workspace 中的 Markdown 吗? + +可以。文件是事实源,watcher 会摄取修改。应保留有效 frontmatter、使用完整 workspace-relative wikilink,并避免同时由多个编辑器无条件覆盖同一文件。 + +## 可以把服务暴露到公网吗? + +默认配置不适合直接公网暴露。服务包含写入和删除 Job,HTTP CORS 宽松,且没有通用认证层。请使用受控网络或带 TLS、认证、访问控制的反向代理,并限制 `service.jobs`。 + +## 怎样备份和迁移? + +停止写入后备份整个 workspace。`session/`、`resource/`、`daily/` 和 `digest/` 是最重要的用户数据;`metadata/` 可以随同备份,也可以从源文件重建。详见[诊断、备份与恢复](./operations.md)。 + +## Studio 找不到怎么办? + +基础 `reme-ai` 包不包含前端资源。安装 `reme-ai[web]` 或 `reme-ai[core]`,或通过 `service.web_static_dir` 指向构建产物。Studio 缺失不会影响 Job API。 + +## 当前服务到底开放了哪些能力? + +运行: + +```bash +reme help +reme app_config +``` + +静态文档描述默认配置;运行服务可能由自定义配置和插件改变。 diff --git a/docs/zh/index.md b/docs/zh/index.md new file mode 100644 index 00000000..b8e3436b --- /dev/null +++ b/docs/zh/index.md @@ -0,0 +1,36 @@ +--- +layout: home +title: ReMe 文档 +titleTemplate: false +hero: + name: ReMe + text: 让 Agent 真正记住 + tagline: 文件属于你,记忆服务于 Agent。ReMe 将对话和资料沉淀为可读、可编辑、可检索、相互链接的本地文件。 + image: + src: /reme-logo.svg + alt: ReMe Logo + actions: + - theme: brand + text: 快速开始 + link: /zh/quick_start + - theme: alt + text: 查看配置 + link: /zh/configuration +features: + - icon: 📁 + title: 文件即记忆 + details: Workspace 文件是持久事实源;索引、图谱和缓存都可以重新构建。 + link: /zh/memory_as_file + - icon: 🧠 + title: 自动记忆 + details: 将对话和资料写入 daily,自动整理为互相关联的长期 digest。 + link: /zh/auto_memory + - icon: 🔎 + title: 检索与图谱 + details: 结合关键词、可选向量检索与 wikilink 图谱,渐进式展开上下文。 + link: /zh/memory_search + - icon: 🔌 + title: Agent 集成 + details: 通过 CLI、HTTP、MCP 和宿主适配器接入已有 Agent。 + link: /zh/integrations +--- diff --git a/docs/zh/integrations.md b/docs/zh/integrations.md new file mode 100644 index 00000000..ce3dd841 --- /dev/null +++ b/docs/zh/integrations.md @@ -0,0 +1,88 @@ +--- +title: Agent 集成 +description: 通过 CLI、HTTP、MCP、Skill 和宿主适配器把 ReMe 接入 Agent。 +--- + +# Agent 集成 + +ReMe 把记忆能力放在独立服务和用户拥有的 workspace 中。Agent 可以通过标准接口调用同一套记忆,而不必把存储逻辑绑定到某一个模型或宿主。 + +## 选择接入方式 + +| 场景 | 推荐方式 | +|---|---| +| 本机脚本或 Hook | ReMe CLI | +| 应用后端 | HTTP Client | +| 支持工具协议的 Agent | MCP | +| TypeScript Agent | `@agentscope-ai/reme` | +| Claude Code | MCP + Skill + Stop Hook | +| Hermes Agent | Memory provider adapter | +| Codex 或其他 coding agent | `reme_memory` Skill 或 MCP | + +## 通用接入循环 + +一个完整但可控的 Agent 记忆循环通常包含: + +1. 会话开始或回答前,用 `search` 找到相关记忆; +2. 对高价值结果使用 `read`,必要时用 `traverse` 展开关系; +3. 在回答中保留 workspace-relative 来源路径; +4. 会话结束后,把原始消息交给 `auto_memory`; +5. 由后台或定时任务把 daily 内容整理到 digest。 + +搜索不到内容时应明确返回空结果,不应把模型推测当成历史记忆。 + +## MCP + +默认 HTTP 服务在 `http://127.0.0.1:2333/mcp` 提供 streamable HTTP MCP。常用工具包括: + +- `search` +- `read` +- `traverse` +- `list` +- `auto_memory` +- `proactive` + +根据宿主风险模型,可以用 `service.jobs` 只暴露只读工具,或将写入工具放在单独配置中。 + +## CLI 和 Skill + +仓库中的 `skills/reme_memory/SKILL.md` 描述了一个通用 Agent 工作流,包括安装检查、服务发现、检索、读取和写入边界。它适合能够执行本地命令的 Agent。 + +Skill 不应: + +- 未经允许安装或升级 Python 环境; +- 发现端口冲突后停止未知进程; +- 把召回的工具结果再次写入对话来源; +- 将密钥或敏感信息写入记忆。 + +## TypeScript、OpenClaw 与 DeepSeek Harness + +统一 HTTP 客户端和包能力见 [TypeScript Agent 集成](./integrations/typescript.md)。宿主的完整安装、配置和运行说明见 [DeepSeek Harness](./integrations/dsh.md) 与 [OpenClaw](./integrations/openclaw.md) 指南。它们包含: + +- HTTP Client; +- DeepSeek Harness adapter; +- OpenClaw adapter; +- 构建与发布检查。 + +## Claude Code + +仓库的 `integrations/claude_code/` 提供: + +- streamable HTTP MCP 配置; +- `reme-memory` Skill; +- 会话停止时调用 `auto_memory_cc` 的 Hook。 + +完整安装步骤以仓库中的 `integrations/claude_code/README.md` 为准。 + +## Hermes Agent + +`integrations/hermes_agent/` 提供 memory provider:模型调用前检索相关记忆,每轮结束后异步调用 `auto_memory`。完整配置见该目录 README。 + +## 生产接入建议 + +- 明确选择稳定、绝对的 `workspace_dir`; +- 启动前复用 `reme find_reme` 发现的服务; +- 以 `reme help` 为当前 Job 契约; +- 为写入动作设置超时和失败日志; +- 不因记忆服务暂时不可用而阻塞宿主的核心回答流程; +- 对远程访问使用认证、TLS 和最小 Job allowlist。 diff --git a/docs/zh/integrations/claude-code.md b/docs/zh/integrations/claude-code.md new file mode 100644 index 00000000..17f78ab4 --- /dev/null +++ b/docs/zh/integrations/claude-code.md @@ -0,0 +1,67 @@ +--- +title: Claude Code 集成 +description: 通过 MCP、reme-memory Skill 和 Stop Hook 将 Claude Code 连接到 ReMe。 +--- + +# Claude Code 集成 + +ReMe 的 Claude Code 插件提供长期记忆召回,并在每次会话结束后异步记录对话。Daily 到 digest 的整理仍由共享的 ReMe 服务负责。 + +## 能力 + +- 通过 MCP 使用 `search`、`traverse`、`daily_list`、`frontmatter_read`、`read`、`auto_memory_cc` 等工具; +- `reme-memory` Skill 在回答前召回长期记忆并保留来源路径; +- Stop Hook 只把 Claude Code `session_id` 传给服务端,服务端从本地 transcript 解析会话; +- 记录在脱离 Claude Code 的后台进程中进行,不延迟退出;服务不可用时记录日志并结束。 + +## 部署模型 + +插件连接到用户预先启动的共享 HTTP MCP 服务,不为每个 Claude Code 窗口创建 ReMe。这样所有窗口共享一个 workspace、一组 watcher 和一次 dream cron。 + +## 准备 ReMe + +```bash +pip install "reme-ai[core]" +``` + +在稳定目录配置 LLM 环境,然后启动: + +```bash +reme start service.backend=http +``` + +默认 JSON Job API 和 MCP 地址分别位于同一个 `127.0.0.1:2333` 服务,MCP 路径是 `/mcp`。使用其他端口时,必须同步修改插件的 `.mcp.json`。 + +默认搜索使用 BM25;只有启用向量检索时才需要 Embedding 配置。 + +## 安装插件 + +在 Claude Code 中运行: + +```text +/plugin marketplace add ./integrations/claude_code +/plugin install reme@reme-marketplace +``` + +重启 Claude Code,再运行 `/mcp`,确认 `reme` server 和工具已经连接。 + +## Hook 与路径 + +- MCP 配置:`integrations/claude_code/reme/.mcp.json`; +- 自动记忆 Hook:`integrations/claude_code/reme/hooks/auto_memory.py`; +- Hook 日志:`integrations/claude_code/reme/logs/auto_memory_hook.log`; +- 默认 transcript 根目录:`~/.claude/projects`; +- 可通过 `CLAUDE_CONFIG_DIR` 修改 transcript 根目录; +- 可通过 `REME_HOST`、`REME_PORT` 覆盖 Hook 使用的服务地址。 + +Hook 需要 `python3` 位于 `PATH`。MCP 工具名前缀可能随 Claude Code 版本包含 server segment;Skill 使用 `mcp__reme__*` 匹配这一差异。 + +## 验证 + +1. `reme health_check` 返回健康; +2. Claude Code `/mcp` 显示 ReMe; +3. `reme-memory` 能召回一条已存在记忆; +4. 完成测试会话后,Hook 日志没有错误; +5. 对应内容出现在当天 daily note 中。 + +英文原始部署说明位于 `integrations/claude_code/README.md`。 diff --git a/docs/zh/integrations/hermes.md b/docs/zh/integrations/hermes.md new file mode 100644 index 00000000..2371b1f8 --- /dev/null +++ b/docs/zh/integrations/hermes.md @@ -0,0 +1,59 @@ +--- +title: Hermes Agent 集成 +description: 使用 ReMe memory provider 在 Hermes 调用模型前召回、每轮结束后异步记录。 +--- + +# Hermes Agent 集成 + +Hermes memory provider 连接到一个已经运行的 ReMe HTTP 服务,在每次模型调用前召回相关记忆,并在用户/助手回合完成后异步调用 `auto_memory`。 + +## Workspace 隔离 + +ReMe 的搜索范围是一个完整 workspace。多个 Hermes profile 指向同一个 workspace 时会共享召回结果;需要隔离时,为每个 profile 使用独立 workspace 和端点。 + +```bash +reme start \ + workspace_dir="/absolute/path/to/reme-hermes-default" \ + service.backend=http \ + service.host=127.0.0.1 \ + service.port=2333 +``` + +自动记忆需要 LLM;默认 BM25 搜索不需要 Embedding。 + +## 安装与配置 + +```bash +hermes plugins install agentscope-ai/ReMe/integrations/hermes_agent +hermes memory setup +``` + +选择 `reme`,接受默认的 `http://127.0.0.1:2333`,或输入上一步使用的端点。Setup 会先调用 `health_check`,只有新端点健康时才替换现有 provider 配置。 + +配置存放在 `$HERMES_HOME/reme.json`: + +```json +{ + "endpoint": "http://127.0.0.1:2333", + "request_timeout": 600.0, + "recall_timeout": 5.0, + "health_timeout": 2.0, + "health_retry_seconds": 30.0, + "shutdown_timeout": 30.0, + "recall_limit": 5 +} +``` + +运行 `hermes memory status` 检查安装和配置。新的 Hermes 会话还会重新检查端点健康状态。 + +## 生命周期和失败行为 + +- `prefetch` 调用 ReMe `search`,Hermes 将结果放入受保护的 memory context; +- `sync_turn` 把完成的回合加入串行后台写队列,再调用 `auto_memory`; +- cron、flush 和 subagent context 不写入对话记忆; +- 健康检查失败后,在 cooldown 结束前暂停召回和记录; +- 召回与写入有独立 cooldown,单项失败不会关闭另一项; +- 召回使用较短超时,避免慢搜索长期阻塞模型调用; +- shutdown 会在有限时间内排空写队列,ReMe 服务仍由用户独立管理。 + +英文权威安装说明位于 `integrations/hermes_agent/README.md`。 diff --git a/docs/zh/operations.md b/docs/zh/operations.md new file mode 100644 index 00000000..43e4bcbd --- /dev/null +++ b/docs/zh/operations.md @@ -0,0 +1,106 @@ +--- +title: 诊断、备份与恢复 +description: ReMe 服务健康检查、日志、索引维护、备份迁移和常见恢复流程。 +--- + +# 诊断、备份与恢复 + +ReMe 的恢复原则是:保护 workspace 中的用户文件,通过源文件重建索引、图谱和 catalog。不要为了修复索引而删除或改写用户记忆。 + +## 快速诊断 + +依次执行: + +```bash +reme find_reme +reme version +reme health_check +reme status +reme app_config +``` + +- `find_reme`:服务是否存在,以及实际 host、port、PID; +- `version`:CLI 能否成功访问服务; +- `health_check`:组件健康状态; +- `status`:状态组件的内存估算和进程 RSS; +- `app_config`:隐藏密钥后的实际生效配置。 + +## 日志 + +`log_to_console` 和 `log_to_file` 控制日志目标。排查启动失败时先查看第一条异常,而不是后续客户端连接错误。 + +常见类别: + +| 现象 | 优先检查 | +|---|---| +| CLI 找不到服务 | `reme find_reme`、启动目录、端口和进程状态 | +| 自动记忆失败 | LLM backend、model、API key、base URL | +| 只有 BM25 结果 | embedding 组件是否真正接入 `file_store` | +| 新文件没有进入搜索 | 文件所在目录、后缀、watcher 和 `health_check` | +| Studio 空白但 API 正常 | web extra、静态构建路径和浏览器控制台 | +| 插件安装后不可用 | 当前 Python 解释器、`plugins` 配置、服务重启 | + +## 索引维护 + +```bash +reme reindex scope=all +reme reindex scope=bm25 +reme reindex scope=embedding +``` + +`reindex` 从当前 `file_chunks` 重建 BM25 和/或 embedding 派生索引。它不会扫描 workspace、重新分块或重建 wikilink 图谱。 + +如果问题发生在文件摄取阶段,应先确认后台 watcher 正常运行;不能把 `reindex` 当成通用“重新扫描”命令。 + +Daily 索引页可单独重建: + +```bash +reme daily_reindex date=2026-09-04 +``` + +## 备份 + +停止写入或停止服务后,优先备份整个 workspace。最重要的目录是: + +- `session/`:原始对话来源; +- `resource/`:外部资料; +- `daily/`:每日记忆; +- `digest/`:长期记忆。 + +`metadata/` 包含索引、图谱和 file catalog。一起备份可以加速恢复,但它不是唯一事实源。 + +不要只备份进程目录下偶然生成的 `.reme/`;生产使用应明确设置稳定的绝对 `workspace_dir`。 + +## 迁移 workspace + +1. 停止旧服务,避免迁移期间继续写入。 +2. 复制完整 workspace,并保留文件时间信息。 +3. 使用新的绝对路径启动: + +```bash +reme start workspace_dir=/new/location/reme-memory +``` + +4. 运行 `health_check`、`status` 和一次代表性 `search`。 +5. 如果 embedding 模型或维度发生变化,再重建 embedding 索引。 + +普通 Markdown 和资源文件可以使用版本控制或同步工具;包含敏感对话的 workspace 不应推送到公开仓库。 + +## 从派生状态故障恢复 + +在确认备份可用前,不要删除任何内容。恢复顺序应为: + +1. 保留 `session/`、`resource/`、`daily/` 和 `digest/`; +2. 记录当前配置和组件 backend; +3. 确认故障只发生在 `metadata/`; +4. 将有问题的派生状态移到隔离备份位置; +5. 用同一配置启动 ReMe,让 watcher 从源文件重建; +6. 验证搜索、图谱和 Daily 索引。 + +具体 metadata 文件属于实现细节,不应在自动化脚本里依赖其内部格式。 + +## 并发编辑 + +Studio 或其他编辑器保存完整文件时,应使用 `stat` 返回的 mtime 作为 `save.expected_mtime`。如果文件在打开后被外部修改,保存会失败,从而避免静默覆盖。 + +文件 Job 会校验 workspace containment,并对同一路径加锁。不要绕过这些 Job 直接向不受控制的绝对路径写入。 diff --git a/docs/zh/plugin_development.md b/docs/zh/plugin_development.md new file mode 100644 index 00000000..67a6d10b --- /dev/null +++ b/docs/zh/plugin_development.md @@ -0,0 +1,101 @@ +--- +title: 插件开发 +description: 创建、注册、配置、测试和发布 ReMe 插件。 +--- + +# 插件开发 + +ReMe 插件是一个普通 Python distribution,通过 `reme.plugins` entry-point group 暴露 package-level `plugin.yaml`。插件可以注册新的 Step、Component backend,并提供默认 Application 配置。 + +## 最小结构 + +```text +my-plugin/ +├── pyproject.toml +└── src/my_plugin/ + ├── __init__.py + ├── plugin.yaml + └── steps.py +``` + +`pyproject.toml`: + +```toml +[project.entry-points."reme.plugins"] +my-plugin = "my_plugin" +``` + +`plugin.yaml`: + +```yaml +name: my-plugin +backends: + my_step: my_plugin.steps:MyStep +application_defaults: + jobs: + my_action: + backend: base + description: Run my plugin action + parameters: + type: object + properties: + text: { type: string } + required: [text] + steps: + - backend: my_step +``` + +## 实现 Step + +```python +from reme.components.component_registry import R +from reme.steps.base_step import BaseStep + + +@R.register("my_step") +class MyStep(BaseStep): + async def execute(self): + self.context.response.answer = self.context.data["text"] +``` + +Step 实例属于单次 Job 调用。跨调用的内存状态应放在带命名空间的 `app_context.metadata`;需要生命周期、锁或持久化时,应升级为 Component 或 workspace 文件。 + +## 配置合并 + +`application_defaults` 是不完整的 `ApplicationConfig`。合并顺序为: + +```text +插件默认值 < 选中/default 配置 < CLI 覆盖 +``` + +插件不应自动修改用户的配置文件。只有在 Application 的 `plugins` 列表中显式启用后,插件 backend 才会加入该 Application 的局部 registry。 + +## 本地验证 + +```bash +reme plugins validate ./path/to/my-plugin +reme plugins install ./path/to/my-plugin --editable +reme plugins list +reme plugins show my-plugin +reme start plugins='["my-plugin"]' +reme my_action text=hello +``` + +校验会导入插件代码,因此只应对可信源码执行。 + +## 测试边界 + +- 使用 `tmp_path` 创建 workspace; +- Mock 网络、模型和子进程; +- 验证插件未启用时不会污染 built-in registry; +- 验证默认配置与显式配置的合并优先级; +- 验证后台任务、客户端和 executor 都由 Component 生命周期关闭; +- 验证失败不会删除或重写用户源文件。 + +仓库中的 `plugins/daily_paper`、`plugins/auto-fin`、`plugins/lme` 和 `plugins/beam` 是完整参考实现。 + +## 兼容性 + +迁移期间仍兼容旧的 Python Plugin descriptor 和 `reme.configs` entry point,但新插件应使用 `plugin.yaml`。不要依赖进程级全局 enable/disable 状态;插件启用始终属于具体 Application 配置。 + +包管理命令、升级与卸载行为见[插件管理](./plugin_management.md)。 diff --git a/docs/zh/plugin_management.md b/docs/zh/plugin_management.md index f9f735f8..a0cdfde7 100644 --- a/docs/zh/plugin_management.md +++ b/docs/zh/plugin_management.md @@ -169,6 +169,15 @@ curl -s http://127.0.0.1:2333/auto_fin \ 自定义应用配置需要提供插件的运行依赖,包括 `agent_wrapper.default`,以及 Auto Fin 使用的 `search` 和 `read` Jobs。 +## Benchmark 应用配置 + +[LME](../../plugins/lme/README_ZH.md) 和 [BEAM](../../plugins/beam/README_ZH.md) 插件通过 +`plugin.yaml` 注册 backend 和插件拥有的 Job。ReMe 内置的 `benchmark` 配置提供公共核心 Job 和 +Component,并且不继承 `default`,因此不包含默认后台和定时任务。先安装所需的 benchmark 插件, +再使用 `config=benchmark`,同时指定 `plugins=["lme"]` 或 `plugins=["beam"]`。仓库内的 benchmark +runner 会自动启用对应的已安装插件;editable 安装可让本地源码修改直接生效。数据集 runner 仍位于 +`benchmark/`。 + ## 卸载插件 这里使用插件 entry-point 名称,它不一定等于 distribution 名称: diff --git a/docs/zh/quick_start.md b/docs/zh/quick_start.md index f8f14340..3c57f626 100644 --- a/docs/zh/quick_start.md +++ b/docs/zh/quick_start.md @@ -1,5 +1,12 @@ +--- +title: 快速开始 +description: 安装并启动 ReMe,完成文件写入、检索和自动记忆的第一个闭环。 +--- + # 快速开始 +本页用于完成第一次可运行闭环。需要完整配置字段时查看[基础配置](./configuration.md);接入 HTTP 或 MCP 时查看[服务与部署](./services.md)。 + ## 安装 ReMe 要求 Python 3.11+。 @@ -229,3 +236,5 @@ reme start \ ```bash reme start config=/path/to/custom.yaml ``` + +下一步可以查看 [CLI 参考](./reference/cli.md)、[Job API 参考](./reference/jobs.md)和[诊断、备份与恢复](./operations.md)。 diff --git a/docs/zh/reference/cli.md b/docs/zh/reference/cli.md new file mode 100644 index 00000000..3caa7cde --- /dev/null +++ b/docs/zh/reference/cli.md @@ -0,0 +1,87 @@ +--- +title: CLI 参考 +description: ReMe 命令行语法、服务调用、配置覆盖与插件命令。 +--- + +# CLI 参考 + +ReMe 的基本语法是: + +```text +reme ACTION key=value ... +``` + +## 启动应用 + +```bash +reme start +reme start config=demo +reme start workspace_dir=/data/reme service.port=8181 +reme start job=search query="关键词" limit=5 +``` + +`start job=` 运行一次性 Job;普通 `start` 启动配置中的 Service。 + +## 调用 Job + +服务运行后,Action 名就是 Job 名: + +```bash +reme help +reme health_check +reme search query="项目决策" limit=10 +reme read path=digest/wiki/project.md start_line=1 end_line=80 +``` + +复杂值使用 JSON: + +```bash +reme auto_memory \ + session_id=example \ + messages='[{"role":"user","content":"记住这条偏好"}]' +``` + +服务选择参数 `backend`、`transport`、`host`、`port`、`timeout`、`command`、`args` 和 `show_metadata` 只用于构造客户端,不会泄漏到 Job 参数。 + +## 配置覆盖 + +```bash +reme start \ + config=/path/to/custom.yaml \ + service.port=8181 \ + service.web_enabled=false \ + plugins='["auto-fin"]' +``` + +参数前的 `-` 或 `--` 可以省略。嵌套键使用点号,数组和对象使用 JSON。 + +## 服务发现 + +```bash +reme find_reme +``` + +成功时输出可复用的服务信息;找不到服务时不会自动启动新进程。 + +## 插件命令 + +插件包管理是本地命令,不经过 HTTP 或 MCP: + +```bash +reme plugins list +reme plugins show auto-fin +reme plugins validate auto-fin +reme plugins install reme-auto-fin +reme plugins uninstall auto-fin +``` + +完整说明见[插件管理](../plugin_management.md)。 + +## 发现当前能力 + +默认 Job 参数见 [Job API 参考](./jobs.md)。运行配置可能由插件和自定义 YAML 改变,因此自动化程序应优先调用: + +```bash +reme help +reme app_config +``` diff --git a/docs/zh/services.md b/docs/zh/services.md new file mode 100644 index 00000000..9cfea053 --- /dev/null +++ b/docs/zh/services.md @@ -0,0 +1,131 @@ +--- +title: 服务与部署 +description: 使用 ReMe 的 HTTP、SSE、MCP 和 Studio 服务,并理解默认安全边界。 +--- + +# 服务与部署 + +ReMe 可以作为本地 HTTP 服务、独立 MCP Server 或一次性 CLI Job 运行。默认模式是在 `127.0.0.1:2333` 启动 HTTP 服务,并在同一进程中提供 JSON API、SSE、MCP 与可选的 ReMe Studio。 + +## HTTP 服务 + +```bash +reme start +reme start service.host=127.0.0.1 service.port=8181 +``` + +普通 Job 暴露为 `POST /`: + +```bash +curl -s http://127.0.0.1:2333/search \ + -H 'Content-Type: application/json' \ + -d '{"query":"用户偏好","limit":5}' +``` + +请求体允许 Job 参数位于顶层。响应遵循 `Response`: + +```json +{ + "success": true, + "answer": "...", + "metadata": {} +} +``` + +未被 Step 捕获的错误会转换成 `success: false` 的响应。 + +## Streaming 与 SSE + +`backend: stream` 的 Job 仍使用 `POST /`,但响应类型是 `text/event-stream`。每个 chunk 使用统一的 streaming schema;失败时会发出错误 chunk,并以终止事件结束。 + +默认 `chat` 是 Stream Job: + +```bash +curl -N http://127.0.0.1:2333/chat \ + -H 'Content-Type: application/json' \ + -d '{"query":"总结我的长期偏好"}' +``` + +MCP 不暴露 Stream Job。 + +## MCP + +默认 HTTP 服务会在 `/mcp` 挂载 streamable HTTP MCP: + +```yaml +service: + backend: http + mcp_enabled: true + mcp_path: /mcp + mcp_stateless_http: false +``` + +也可以改用独立 MCP Service: + +```bash +reme start service.backend=mcp service.transport=stdio +reme start service.backend=mcp service.transport=sse service.port=2333 +reme start service.backend=mcp service.transport=streamable-http service.port=2333 +``` + +MCP tool 来自 `enable_serve: true` 的非流式 Job。`service.jobs` 可以设置允许列表;`injected_job_kwargs` 可以注入服务端管理、调用方不能覆盖的参数。 + +```yaml +service: + backend: http + jobs: [search, read, traverse, auto_memory] + injected_job_kwargs: + tenant_id: local-user + tool_error_on_failure: true +``` + +## ReMe Studio + +安装 `reme-ai[web]` 或 `reme-ai[core]` 后,默认 HTTP 地址同时提供 ReMe Studio: + +```text +http://127.0.0.1:2333/ +``` + +可通过配置关闭或指定自定义构建: + +```yaml +service: + web_enabled: false + # web_static_dir: /absolute/path/to/static +``` + +静态资源缺失不会阻止 Job API 启动。 + +## 一次性 Job + +需要脚本式执行而不常驻服务时: + +```bash +reme start job=search query="用户偏好" limit=5 +``` + +它会切换到一次性 CLI Service,但仍经过正常的 Application、Component 和 Job 生命周期。 + +## 服务发现 + +```bash +reme find_reme +``` + +ReMe 会记录本机运行服务的启动参数。普通 `reme ` 优先使用实际运行服务的 backend、host、port 和 transport;找不到运行服务时才回退到本地配置。 + +## 安全边界 + +ReMe 默认定位为本地服务: + +- 默认绑定 `127.0.0.1`; +- HTTP CORS 配置允许任意 origin; +- Job 可进行文件写入、移动和删除; +- 当前服务层不提供通用用户认证。 + +不要直接把默认服务暴露到公网。需要远程访问时,在受控网络或带身份认证、TLS、访问控制和请求大小限制的反向代理后部署,并通过 `service.jobs` 只开放必要 Job。 + +## OpenAPI + +HTTP 服务使用 FastAPI。启用 HTTP 服务时,可以通过 `/docs`、`/redoc` 和 `/openapi.json` 查看当前配置实际注册的端点;Studio 的 SPA fallback 不会覆盖这些保留路径。 diff --git a/github-pages/README.md b/github-pages/README.md index 2be3e6a4..c4b09810 100644 --- a/github-pages/README.md +++ b/github-pages/README.md @@ -1,8 +1,8 @@ -# ReMe GitHub Pages +# ReMe documentation site -This directory contains the standalone Vite documentation site published at . The -GitHub Pages fallback is . It does not depend on the ReMe Studio application in -`reme_studio/`. +This package builds the VitePress site published at . The canonical documentation lives in +`docs/`; selected product, integration, plugin, and benchmark READMEs are mirrored into a disposable source tree during +the build. Do not edit `.generated/` or `dist/`. ## Requirements @@ -11,79 +11,41 @@ GitHub Pages fallback is . It does not de ## Local development -From the repository root: - ```bash cd github-pages -npm install +npm ci npm run dev ``` -Open the URL printed by Vite, normally . The development server watches the frontend source. -When a repository Markdown file changes, restart the development command to regenerate the documentation content. +The development server prints its local URL. Restart it after changing a mirrored README or `reme/config/default.yaml` +so the generated source tree and Job reference are refreshed. Changes under `docs/` are also refreshed on restart. -For subsequent installs or CI-compatible dependency installation, use: - -```bash -npm ci -``` - -## Preview the production build - -Build and start the preview server: +## Validation ```bash +npm test npm run build npm run preview ``` -Open the URL printed by Vite, normally . Production assets use relative paths so the same build -works on both the custom domain and the GitHub Pages project path. +The test suite verifies bilingual core pages, canonical-source mappings, generated Job coverage, and disposable output. +The production build is written to `github-pages/dist/` for the existing GitHub Pages workflow. -The generated `dist/` and `.generated/` directories are disposable build output and are excluded from Git. +## Sources -## Documentation sources - -The build script reads the canonical repository files directly. Do not edit generated copies under `.generated/` or -`dist/`. - -- `README.md` and `README_ZH.md`: project introductions -- `docs/en/` and `docs/zh/`: English and Chinese guides -- `docs/figure/`: documentation images -- `reme_studio/README.md` and `reme_studio/README_ZH.md`: ReMe Studio guide -- `typescript/README.md` and `typescript/README_ZH.md`: TypeScript client, DeepSeek Harness, and OpenClaw integration guide +- `docs/`: canonical guides, VitePress configuration, theme, and brand assets +- `reme/config/default.yaml`: generated callable Job reference +- `reme_studio/README*.md`: ReMe Studio +- `typescript/README*.md`: TypeScript client and adapters - `plugins/*/README*.md`: plugin guides -- `benchmark/{beam,longmemeval,pibench,toolmemory}/README*.md`: benchmark guides and results -- `AGENTS.md`: repository development guide +- `benchmark/*/README*.md`: benchmark guides +- `scripts/generate-content.mjs`: source mirroring and reference generation -To add or reorganize a document in the site navigation, update -[`scripts/generate-content.mjs`](./scripts/generate-content.mjs). Presentation and interaction code lives in `src/`. - -## Project structure - -```text -github-pages/ -├── index.html -├── package.json -├── scripts/ -│ └── generate-content.mjs -├── src/ -│ ├── main.js -│ └── styles.css -└── vite.config.js -``` +When adding a canonical guide, add both `docs/zh/.md` and `docs/en/.md`, then include it in the appropriate +sidebar in `docs/.vitepress/config.mts`. Add repository-owned READMEs to `externalDocuments` in the generator rather than +duplicating their full content under `docs/`. ## Deployment -The repository workflow `.github/workflows/deploy-docs.yml` builds this directory and publishes `dist/` to GitHub Pages. -It runs after relevant documentation or site files change on `main`, and it can also be started manually from the -GitHub Actions page. - -The repository's **Settings → Pages → Build and deployment → Source** must be set to **GitHub Actions**. Its custom -domain must be set to `reme.agentscope.io`; `public/CNAME` preserves that domain in the published artifact. - -Useful links: - -- ReMe documentation: -- GitHub Pages fallback: -- ReMe repository: +`.github/workflows/deploy-docs.yml` uses the reusable documentation build workflow and publishes `dist/` to GitHub +Pages. `public/CNAME` preserves the `reme.agentscope.io` custom domain. diff --git a/github-pages/index.html b/github-pages/index.html deleted file mode 100644 index 3655d5ee..00000000 --- a/github-pages/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - ReMe Documentation - - -
- - - diff --git a/github-pages/package-lock.json b/github-pages/package-lock.json index 726cc77f..48930a39 100644 --- a/github-pages/package-lock.json +++ b/github-pages/package-lock.json @@ -7,21 +7,377 @@ "": { "name": "reme-github-pages", "version": "0.1.0", - "dependencies": { - "dompurify": "^3.2.6", - "marked": "^16.2.1" - }, "devDependencies": { - "vite": "^7.1.1" + "vitepress": "1.6.4", + "yaml": "^2.8.1" }, "engines": { "node": ">=22.13.0" } }, + "node_modules/@algolia/abtesting": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.23.0.tgz", + "integrity": "sha512-j45MBISstltys9QyQ4xf6quRiN1g7vMuwQL9VM4dx8YuRZvCQ173b9royZAx6iAbRX3IB1VnG1z//NuwyQ8jpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.57.0.tgz", + "integrity": "sha512-JVFFujiZUCguk5tz3LZr4fTQxqpIrj4/Jw3SI7kMljSqtfLxYn/s/TWH0J2s4iNfsDpxPhgFGMotCpmDI4kZ8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.57.0.tgz", + "integrity": "sha512-6KqECK4ED3JJQEoDrQWnGPQzElA828xAD4qK5ceawNNyP/LcSvzAoLHjFkoTPksZ/kxj6VUtCRH+IHZesLltng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.57.0.tgz", + "integrity": "sha512-uqpGF3oXYsoCbQq5d7BzNrNTfIfuvJyGP1CKvSW27T9boUg7KOwyxsAw1AX0a3jSW2HrYEJ/NN+Z4MiGivbpeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.57.0.tgz", + "integrity": "sha512-u5NboJVJXDEFplvNnqqX4CxkXPYysjJRj47hOSh9329H8kG5gFLKJBIiS5utMQ+GZm8xQl3Te7NInDk6elEADQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.57.0.tgz", + "integrity": "sha512-uzc0b2LmHAK9/QID4xeo35OG84AkZl4YewkCqawqAOGLjT2eZpM/OZx45ESygMHG30Ws+ZTSdluPtMJcUnrbWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.57.0.tgz", + "integrity": "sha512-dIAhnM6ue/ssa5PjgNfu4g8A4yTojl9ZOUzZU3wIaIKRerL2R/3Emuf9n/D6ICXXP167KC6XCeC7nliSw7cuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.57.0.tgz", + "integrity": "sha512-2TTPTTKSJmCptvhCm4Xf3bBYMqZni+Pgc2hVdqc4l9wsBpSJNVTVIKpnd10OubUgkGcmppVDj1XQqYaf6EnPSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.57.0.tgz", + "integrity": "sha512-W4JseHKt+pzOxlFV+T3MWEG0h4Z2Se5zjoXUD0ewlw8aOWMG/yjRdopUdLQsXULepB/My2tDuZjkwk2sMfsUrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.57.0.tgz", + "integrity": "sha512-BrxJVE0/eLinEPICCD7BKN/2xnt0nkjge70u8zzE2ISP3fuB3tjLgcwpanUycvlHBFLI4gK0l5ol54p6IYuR/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.57.0.tgz", + "integrity": "sha512-Gc29jkeiLKlVfHvyrIgyUHHE+aYTdXEeLfK42rjr5/1TTVsYwUJz0XkvoIBIqfMjcDg6gXeHb1jTUZ0H+SYIlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.57.0.tgz", + "integrity": "sha512-PIPnPN7MP3fp2VAi01BVXhCWmD366ZB2Hkq5TlYKtThd4KxUtMmaaNDpFgVCTXtSIqWVZLJntOHRvxg/sIPd8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.57.0.tgz", + "integrity": "sha512-AX3RlOudXMdTwtwUqdAf5hAVLvXfOZZH1FZh6ALDdrhVLT0TtAIe48N6nYcWcTnwoTxK/wDIQqZ19IMjK8zJAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.57.0.tgz", + "integrity": "sha512-cWZc1dKb7wy9/wPpwMtL1y89gK2G7y2A47Coa7zwf1ydtIeJm4+S+XxoQ2b/ZRiQnrC1YHavLjYUPDCdnT7Khg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", - "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -32,13 +388,13 @@ "aix" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", - "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -49,13 +405,13 @@ "android" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", - "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -66,13 +422,13 @@ "android" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", - "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -83,13 +439,13 @@ "android" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", - "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -100,13 +456,13 @@ "darwin" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", - "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -117,13 +473,13 @@ "darwin" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", - "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -134,13 +490,13 @@ "freebsd" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", - "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -151,13 +507,13 @@ "freebsd" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", - "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -168,13 +524,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", - "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -185,13 +541,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", - "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -202,13 +558,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", - "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -219,13 +575,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", - "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -236,13 +592,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", - "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -253,13 +609,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", - "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -270,13 +626,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", - "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -287,13 +643,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", - "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -304,30 +660,13 @@ "linux" ], "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", - "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", - "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -338,30 +677,13 @@ "netbsd" ], "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", - "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", - "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -372,30 +694,13 @@ "openbsd" ], "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", - "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", - "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -406,13 +711,13 @@ "sunos" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", - "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -423,13 +728,13 @@ "win32" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", - "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -440,13 +745,13 @@ "win32" ], "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", - "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -457,9 +762,33 @@ "win32" ], "engines": { - "node": ">=18" + "node": ">=12" } }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.94", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.94.tgz", + "integrity": "sha512-l8UWzVxKaqZd9ABsE/M/9p6NyGkQnmCnOoZyhQmjlXCtY5PuL2rcWxOFk2l9pk7ux3ERMPkTLE4jl6kQpTkwxA==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@napi-rs/lzma-linux-x64-gnu": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", @@ -827,6 +1156,93 @@ "win32" ] }, + "node_modules/@shikijs/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", + "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", + "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^3.1.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", + "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", + "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", + "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", + "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", + "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -834,26 +1250,483 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "dev": true, "license": "MIT", - "optional": true + "dependencies": { + "@types/unist": "*" + } }, - "node_modules/dompurify": { - "version": "3.4.13", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz", - "integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.2.0.tgz", + "integrity": "sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.42.tgz", + "integrity": "sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.42", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.42.tgz", + "integrity": "sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.42.tgz", + "integrity": "sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.42", + "@vue/compiler-dom": "3.5.42", + "@vue/compiler-ssr": "3.5.42", + "@vue/shared": "3.5.42", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.42.tgz", + "integrity": "sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.10.tgz", + "integrity": "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.10" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz", + "integrity": "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.10", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz", + "integrity": "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.42.tgz", + "integrity": "sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.42.tgz", + "integrity": "sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.42.tgz", + "integrity": "sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.42", + "@vue/runtime-core": "3.5.42", + "@vue/shared": "3.5.42", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.42.tgz", + "integrity": "sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.42", + "@vue/runtime-dom": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.42.tgz", + "integrity": "sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", + "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vueuse/core": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/algoliasearch": { + "version": "5.57.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.57.0.tgz", + "integrity": "sha512-HpND7MBGctOAkd1GoQoDZCGoCpqNTS5NG1LuhElFet3RdLJkwnyTYZXZhXwtpAQPrI36fqQ3eT6KQrdKDTKu3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.23.0", + "@algolia/client-abtesting": "5.57.0", + "@algolia/client-analytics": "5.57.0", + "@algolia/client-common": "5.57.0", + "@algolia/client-insights": "5.57.0", + "@algolia/client-personalization": "5.57.0", + "@algolia/client-query-suggestions": "5.57.0", + "@algolia/client-search": "5.57.0", + "@algolia/ingestion": "1.57.0", + "@algolia/monitoring": "1.57.0", + "@algolia/recommend": "5.57.0", + "@algolia/requester-browser-xhr": "5.57.0", + "@algolia/requester-fetch": "5.57.0", + "@algolia/requester-node-http": "5.57.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/copy-anything": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.1.0.tgz", + "integrity": "sha512-ufbM3smX/Jbnpk5wcQjzd1MgBpzmqfNETUAyZNrGwU9foRlyHoGzMMBBCRzEhQLBjZfFDE1W2ufPXX2vdWkV8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/esbuild": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", - "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -861,53 +1734,49 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=18" + "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.2", - "@esbuild/android-arm": "0.28.2", - "@esbuild/android-arm64": "0.28.2", - "@esbuild/android-x64": "0.28.2", - "@esbuild/darwin-arm64": "0.28.2", - "@esbuild/darwin-x64": "0.28.2", - "@esbuild/freebsd-arm64": "0.28.2", - "@esbuild/freebsd-x64": "0.28.2", - "@esbuild/linux-arm": "0.28.2", - "@esbuild/linux-arm64": "0.28.2", - "@esbuild/linux-ia32": "0.28.2", - "@esbuild/linux-loong64": "0.28.2", - "@esbuild/linux-mips64el": "0.28.2", - "@esbuild/linux-ppc64": "0.28.2", - "@esbuild/linux-riscv64": "0.28.2", - "@esbuild/linux-s390x": "0.28.2", - "@esbuild/linux-x64": "0.28.2", - "@esbuild/netbsd-arm64": "0.28.2", - "@esbuild/netbsd-x64": "0.28.2", - "@esbuild/openbsd-arm64": "0.28.2", - "@esbuild/openbsd-x64": "0.28.2", - "@esbuild/openharmony-arm64": "0.28.2", - "@esbuild/sunos-x64": "0.28.2", - "@esbuild/win32-arm64": "0.28.2", - "@esbuild/win32-ia32": "0.28.2", - "@esbuild/win32-x64": "0.28.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/focus-trap": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", + "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "dependencies": { + "tabbable": "^6.4.0" } }, "node_modules/fsevents": { @@ -925,18 +1794,209 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/marked": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", - "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, "license": "MIT", - "bin": { - "marked": "bin/marked.js" + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" }, - "engines": { - "node": ">= 20" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", + "dev": true, + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.18", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", @@ -956,6 +2016,25 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/oniguruma-to-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", + "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -963,19 +2042,6 @@ "dev": true, "license": "ISC" }, - "node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/postcss": { "version": "8.5.26", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", @@ -1005,6 +2071,70 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.29.8", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.8.tgz", + "integrity": "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact-render-to-string": ">=5" + }, + "peerDependenciesMeta": { + "preact-render-to-string": { + "optional": true + } + } + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { "version": "4.62.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", @@ -1051,6 +2181,31 @@ "fsevents": "~2.3.2" } }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/shiki": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", + "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/langs": "2.5.0", + "@shikijs/themes": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1061,42 +2216,192 @@ "node": ">=0.10.0" } }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" }, "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tabbable": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, "node_modules/vite": { - "version": "7.3.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", - "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.27.0 || ^0.28.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -1105,25 +2410,19 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, - "jiti": { - "optional": true - }, "less": { "optional": true }, @@ -1144,14 +2443,99 @@ }, "terser": { "optional": true - }, - "tsx": { + } + } + }, + "node_modules/vitepress": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", + "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { "optional": true }, - "yaml": { + "postcss": { "optional": true } } + }, + "node_modules/vue": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.42.tgz", + "integrity": "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.42", + "@vue/compiler-sfc": "3.5.42", + "@vue/runtime-dom": "3.5.42", + "@vue/server-renderer": "3.5.42", + "@vue/shared": "3.5.42" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/github-pages/package.json b/github-pages/package.json index 4ace932f..a9a0eb6d 100644 --- a/github-pages/package.json +++ b/github-pages/package.json @@ -7,16 +7,14 @@ "node": ">=22.13.0" }, "scripts": { - "dev": "node scripts/generate-content.mjs && vite", - "build": "node scripts/generate-content.mjs && vite build", - "preview": "vite preview", - "test": "node scripts/generate-content.mjs && node --test tests/*.test.mjs" - }, - "dependencies": { - "dompurify": "^3.2.6", - "marked": "^16.2.1" + "content": "node scripts/generate-content.mjs", + "dev": "npm run content && vitepress dev .generated/site", + "build": "npm run content && vitepress build .generated/site --outDir dist && node scripts/verify-build.mjs", + "preview": "vitepress preview .generated/site --outDir dist", + "test": "npm run content && node --test tests/*.test.mjs" }, "devDependencies": { - "vite": "^7.1.1" + "vitepress": "1.6.4", + "yaml": "^2.8.1" } } diff --git a/github-pages/scripts/generate-content.mjs b/github-pages/scripts/generate-content.mjs index 05054730..46608a1a 100644 --- a/github-pages/scripts/generate-content.mjs +++ b/github-pages/scripts/generate-content.mjs @@ -1,249 +1,236 @@ import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { parse as parseYaml } from "yaml"; const siteDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const repoDir = path.resolve(siteDir, ".."); -const outputDir = path.join(siteDir, ".generated", "content"); +const outputDir = path.join(siteDir, ".generated", "site"); -const navigationGroupOrder = [ - "overview", - "start", - "integration", - "fundamentals", - "automation", - "concepts", - "workspace", - "plugins", - "benchmarks", - "development", +const externalDocuments = [ + ["en/integrations/claude-code.md", "integrations/claude_code/README.md"], + ["en/integrations/hermes.md", "integrations/hermes_agent/README.md"], + ["zh/integrations/typescript.md", "typescript/README_ZH.md"], + ["en/integrations/typescript.md", "typescript/README.md"], + ["zh/integrations/dsh.md", "typescript/docs/dsh.zh-CN.md"], + ["en/integrations/dsh.md", "typescript/docs/dsh.md"], + ["zh/integrations/openclaw.md", "typescript/docs/openclaw.zh-CN.md"], + ["en/integrations/openclaw.md", "typescript/docs/openclaw.md"], + ["zh/workspace/studio.md", "reme_studio/README_ZH.md"], + ["en/workspace/studio.md", "reme_studio/README.md"], + ["zh/plugins/daily-paper.md", "plugins/daily_paper/README_ZH.md"], + ["en/plugins/daily-paper.md", "plugins/daily_paper/README.md"], + ["zh/plugins/auto-fin.md", "plugins/auto-fin/README_ZH.md"], + ["en/plugins/auto-fin.md", "plugins/auto-fin/README.md"], + ["zh/plugins/lme.md", "plugins/lme/README_ZH.md"], + ["en/plugins/lme.md", "plugins/lme/README.md"], + ["zh/plugins/beam.md", "plugins/beam/README_ZH.md"], + ["en/plugins/beam.md", "plugins/beam/README.md"], + ["zh/benchmarks/beam.md", "benchmark/beam/README_ZH.md"], + ["en/benchmarks/beam.md", "benchmark/beam/README.md"], + ["zh/benchmarks/longmemeval.md", "benchmark/longmemeval/README_ZH.md"], + ["en/benchmarks/longmemeval.md", "benchmark/longmemeval/README.md"], + ["zh/benchmarks/pibench.md", "benchmark/pibench/README_ZH.md"], + ["en/benchmarks/pibench.md", "benchmark/pibench/README.md"], + ["zh/benchmarks/toolmemory.md", "benchmark/toolmemory/README_ZH.md"], + ["en/benchmarks/toolmemory.md", "benchmark/toolmemory/README.md"], ]; -const topicOrder = [ - "quick_start", - "plugin_management", - "memory_as_file", - "memory_search", - "auto_memory", - "auto_resource", - "auto_link", - "auto_dream", - "proactive", - "reme_scene", - "framework", - "reme-blog", - "contributing", -]; - -const groups = { - quick_start: "start", - plugin_management: "start", - memory_as_file: "fundamentals", - memory_search: "fundamentals", - auto_memory: "automation", - auto_resource: "automation", - auto_link: "automation", - auto_dream: "automation", - proactive: "automation", - reme_scene: "concepts", - framework: "concepts", - "reme-blog": "concepts", - contributing: "development", +const externalDocumentRewrites = { + "typescript/README.md": [ + ["(./README_ZH.md)", "(/zh/integrations/typescript)"], + ["(./docs/dsh.md)", "(/en/integrations/dsh)"], + ["(./docs/dsh.zh-CN.md)", "(/zh/integrations/dsh)"], + ["(./docs/openclaw.md)", "(/en/integrations/openclaw)"], + ["(./docs/openclaw.zh-CN.md)", "(/zh/integrations/openclaw)"], + ["(./figures/dsh/", "(/figures/dsh/"], + ], + "typescript/README_ZH.md": [ + ["(./README.md)", "(/en/integrations/typescript)"], + ["(./docs/dsh.md)", "(/en/integrations/dsh)"], + ["(./docs/dsh.zh-CN.md)", "(/zh/integrations/dsh)"], + ["(./docs/openclaw.md)", "(/en/integrations/openclaw)"], + ["(./docs/openclaw.zh-CN.md)", "(/zh/integrations/openclaw)"], + ["(./figures/dsh/", "(/figures/dsh/"], + ], + "typescript/docs/dsh.md": [ + ["(./dsh.zh-CN.md)", "(/zh/integrations/dsh)"], + ["(../figures/dsh/", "(/figures/dsh/"], + ], + "typescript/docs/dsh.zh-CN.md": [ + ["(./dsh.md)", "(/en/integrations/dsh)"], + ["(../figures/dsh/", "(/figures/dsh/"], + ], + "typescript/docs/openclaw.md": [ + ["(./openclaw.zh-CN.md)", "(/zh/integrations/openclaw)"], + ], + "typescript/docs/openclaw.zh-CN.md": [ + ["(./openclaw.md)", "(/en/integrations/openclaw)"], + ], }; -const localizedTitles = { - quick_start: { zh: "快速开始", en: "Quick Start" }, - plugin_management: { zh: "插件管理", en: "Plugin Management" }, - memory_as_file: { zh: "文件即记忆", en: "Memory as File" }, - memory_search: { zh: "记忆检索", en: "Memory Search" }, - auto_memory: { zh: "自动记忆", en: "Auto Memory" }, - auto_resource: { zh: "自动资料整理", en: "Auto Resource" }, - auto_link: { zh: "自动关联", en: "Auto Link" }, - auto_dream: { zh: "自动沉淀", en: "Auto Dream" }, - proactive: { zh: "主动发现", en: "Proactive" }, - reme_scene: { zh: "ReMe 应用场景", en: "ReMe Application Scenarios" }, - framework: { zh: "ReMe 代码框架", en: "ReMe Framework" }, - "reme-blog": { zh: "ReMe 博客", en: "ReMe Blog" }, - contributing: { zh: "开源与贡献", en: "Open Source and Contributing" }, +const groupNames = { + zh: { + system: "系统与诊断", + memory: "记忆演化", + retrieval: "检索与图谱", + daily: "Daily Note", + files: "文件操作", + }, + en: { + system: "System and diagnostics", + memory: "Memory evolution", + retrieval: "Retrieval and graph", + daily: "Daily notes", + files: "File operations", + }, }; -const productDocuments = [ - { - slug: "typescript", - source: "typescript", - titles: { zh: "TypeScript Agent 集成", en: "TypeScript Agent Integrations" }, - descriptions: { - zh: "配置统一 HTTP client,以及 DeepSeek Harness 和 OpenClaw 原生适配器。", - en: "Configure the shared HTTP client and native DeepSeek Harness and OpenClaw adapters.", - }, - group: "integration", - }, - { - slug: "studio", - source: "reme_studio", - titles: { zh: "ReMe 工作台", en: "ReMe Studio" }, - descriptions: { - zh: "浏览、编辑和搜索本地记忆,并探索记忆图谱。", - en: "Browse, edit, search, and explore local memory from the web workspace.", - }, - group: "workspace", - }, - { - slug: "daily-paper", - source: "plugins/daily_paper", - titles: { zh: "每日论文插件", en: "Daily Paper Plugin" }, - descriptions: { - zh: "发现论文、解析 PDF,并生成阅读笔记与每日简报。", - en: "Discover papers, analyze PDFs, and produce reading notes and a daily brief.", - }, - group: "plugins", - }, - { - slug: "auto-fin", - source: "plugins/auto-fin", - titles: { zh: "Auto Fin 插件", en: "Auto Fin Plugin" }, - descriptions: { - zh: "结合最新财联社新闻与本地历史记忆生成研究报告。", - en: "Research recent CLS news with historical context from local memory.", - }, - group: "plugins", - }, - { - slug: "beam", - source: "benchmark/beam", - titles: { zh: "BEAM", en: "BEAM" }, - descriptions: { - zh: "评测大规模记忆检索能力。", - en: "Evaluate memory retrieval at scale.", - }, - group: "benchmarks", - }, - { - slug: "longmemeval", - source: "benchmark/longmemeval", - titles: { zh: "LongMemEval", en: "LongMemEval" }, - descriptions: { - zh: "评测跨会话长期记忆问答能力。", - en: "Evaluate long-term, cross-session memory question answering.", - }, - group: "benchmarks", - }, - { - slug: "pibench", - source: "benchmark/pibench", - titles: { zh: "π-Bench", en: "π-Bench" }, - descriptions: { - zh: "评测带持久记忆的个人智能体。", - en: "Evaluate personal agents with persistent memory.", - }, - group: "benchmarks", - }, - { - slug: "toolmemory", - source: "benchmark/toolmemory", - titles: { zh: "Tool Memory / ExpG", en: "Tool Memory / ExpG" }, - descriptions: { - zh: "通过经验驱动的自适应指导增强 Agent 工具使用。", - en: "Improve agent tool use through experience-driven adaptive guidance.", - }, - group: "benchmarks", - }, -]; +const jobGroups = { + version: "system", + app_config: "system", + chat: "system", + health_check: "system", + status: "system", + help: "system", + auto_dream: "memory", + auto_memory: "memory", + auto_memory_cc: "memory", + auto_resource: "memory", + proactive: "memory", + traverse: "retrieval", + graph_snapshot: "retrieval", + reindex: "retrieval", + search: "retrieval", + node_search: "retrieval", + daily_list: "daily", + daily_reindex: "daily", + daily_write: "daily", + frontmatter_delete: "files", + frontmatter_read: "files", + frontmatter_update: "files", + stat: "files", + list: "files", + move: "files", + delete: "files", + read: "files", + load: "files", + read_image: "files", + write: "files", + save: "files", + edit: "files", +}; -const sharedDocuments = [ - { - id: "agents-guide", - path: "AGENTS.md", - sourcePath: "AGENTS.md", - titles: { - zh: "Agent 开发指南", - en: "Agent Development Guide", - }, - description: "Repository contracts, lifecycle rules, safety boundaries, and validation.", - group: "development", - language: "shared", - }, -]; - -async function markdownTitle(filePath) { - const source = await readFile(filePath, "utf8"); - return source.match(/^#\s+(.+)$/m)?.[1]?.replace(/[`*_]/g, "") || path.basename(filePath, ".md"); +function typeLabel(schema = {}) { + if (schema.oneOf) return schema.oneOf.map(typeLabel).join(" or "); + if (schema.type === "array") return `${typeLabel(schema.items || {})}[]`; + return schema.type || "any"; } -async function buildManifest() { - const documents = [ - { - id: "readme-zh", - path: "README_ZH.md", - sourcePath: "README_ZH.md", - title: "ReMe 项目介绍", - description: "核心理念、快速开始、使用场景与社区入口。", - group: "overview", - language: "zh", - }, - { - id: "readme-en", - path: "README.md", - sourcePath: "README.md", - title: "Introducing ReMe", - description: "Core ideas, quick start, use cases, and community resources.", - group: "overview", - language: "en", - }, - ]; +function markdownCell(value) { + if (value === undefined) return "—"; + const rendered = typeof value === "string" ? value : JSON.stringify(value); + return rendered + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll("|", "\\|") + .replaceAll("\n", " "); +} - for (const language of ["zh", "en"]) { - for (const topic of topicOrder) { - const sourcePath = `docs/${language}/${topic}.md`; - documents.push({ - id: `${language}-${topic}`, - path: sourcePath, - sourcePath, - title: localizedTitles[topic]?.[language] || (await markdownTitle(path.join(repoDir, sourcePath))), - description: "", - group: groups[topic], - language, - }); - } +function buildJobReference(config, language) { + const isZh = language === "zh"; + const jobs = Object.entries(config.jobs || {}).filter(([, job]) => !["background", "cron"].includes(job.backend)); + const sections = new Map(); - for (const product of productDocuments) { - const filename = language === "zh" ? "README_ZH.md" : "README.md"; - documents.push({ - id: `${product.slug}-${language}`, - path: `${product.source}/${filename}`, - sourcePath: `${product.source}/${filename}`, - title: product.titles[language], - description: product.descriptions[language], - group: product.group, - language, - }); - } + for (const [name, job] of jobs) { + const group = jobGroups[name] || "system"; + if (!sections.has(group)) sections.set(group, []); + sections.get(group).push([name, job]); } - return [...documents, ...sharedDocuments].sort( - (left, right) => navigationGroupOrder.indexOf(left.group) - navigationGroupOrder.indexOf(right.group), - ); + const lines = [ + "---", + `title: ${isZh ? "Job API 参考" : "Job API Reference"}`, + `description: ${isZh ? "从默认配置自动生成的可调用 Job、参数和服务边界。" : "Callable jobs, parameters, and service boundaries generated from the default configuration."}`, + "---", + "", + `# ${isZh ? "Job API 参考" : "Job API Reference"}`, + "", + isZh + ? "本页从 `reme/config/default.yaml` 自动生成。它描述默认应用中的可调用 Job;插件和自定义配置可以增加、删除或覆盖 Job。运行 `reme help` 可查看当前服务的实际能力。" + : "This page is generated from `reme/config/default.yaml`. It describes callable jobs in the default application; plugins and custom configurations may add, remove, or override jobs. Run `reme help` to inspect the active service.", + "", + isZh + ? "> 后台 Job 和 Cron Job 不通过服务暴露,因此不列入调用参考。" + : "> Background and cron jobs are not service-exposed and are omitted from the callable reference.", + "", + ]; + + for (const [group, entries] of sections) { + lines.push(`## ${groupNames[language][group]}`, ""); + for (const [name, job] of entries) { + const properties = job.parameters?.properties || {}; + const required = new Set(job.parameters?.required || []); + lines.push(`### \`${name}\``, "", markdownCell(job.description || ""), ""); + lines.push("```bash", `reme ${name}${Object.keys(properties).length ? " ..." : ""}`, "```", ""); + if (!Object.keys(properties).length) { + lines.push(isZh ? "无参数。" : "No parameters.", ""); + continue; + } + lines.push( + isZh + ? "| 参数 | 类型 | 必填 | 默认值 | 说明 |" + : "| Parameter | Type | Required | Default | Description |", + "|---|---|---:|---|---|", + ); + for (const [parameter, schema] of Object.entries(properties)) { + lines.push( + `| \`${parameter}\` | \`${markdownCell(typeLabel(schema))}\` | ${required.has(parameter) ? (isZh ? "是" : "yes") : (isZh ? "否" : "no")} | ${markdownCell(schema.default)} | ${markdownCell(schema.description || "—")} |`, + ); + } + lines.push(""); + } + } + return `${lines.join("\n")}\n`; } await rm(path.join(siteDir, ".generated"), { recursive: true, force: true }); await mkdir(outputDir, { recursive: true }); -await cp(path.join(siteDir, "public", "favicon.svg"), path.join(siteDir, ".generated", "favicon.svg")); -await cp(path.join(siteDir, "public", "CNAME"), path.join(siteDir, ".generated", "CNAME")); - -for (const file of ["README.md", "README_ZH.md", "AGENTS.md"]) { - await cp(path.join(repoDir, file), path.join(outputDir, file)); -} -await cp(path.join(repoDir, "docs"), path.join(outputDir, "docs"), { +await cp(path.join(repoDir, "docs"), outputDir, { recursive: true, - filter: (source) => path.basename(source) !== ".DS_Store", + filter: (source) => ![".DS_Store", "plans"].includes(path.basename(source)), }); -for (const product of productDocuments) { - await mkdir(path.join(outputDir, product.source), { recursive: true }); - for (const filename of ["README.md", "README_ZH.md"]) { - await cp(path.join(repoDir, product.source, filename), path.join(outputDir, product.source, filename)); - } +await cp(path.join(siteDir, "public", "CNAME"), path.join(outputDir, "public", "CNAME")); +await cp(path.join(repoDir, "docs/figure/reme-icon.svg"), path.join(outputDir, "public", "reme-icon.svg")); +await cp(path.join(repoDir, "docs/figure/reme-logo-fashion.svg"), path.join(outputDir, "public", "reme-logo.svg")); + +const sourceMap = {}; +for (const [destination, source] of externalDocuments) { + const destinationPath = path.join(outputDir, destination); + await mkdir(path.dirname(destinationPath), { recursive: true }); + let content = await readFile(path.join(repoDir, source), "utf8"); + for (const [from, to] of externalDocumentRewrites[source] || []) content = content.replaceAll(from, to); + await writeFile(destinationPath, content); + sourceMap[destination] = source; } -await writeFile( - path.join(outputDir, "manifest.json"), - `${JSON.stringify({ documents: await buildManifest() }, null, 2)}\n`, -); + +await cp(path.join(repoDir, "typescript/figures/dsh"), path.join(outputDir, "public/figures/dsh"), { + recursive: true, +}); + +for (const language of ["zh", "en"]) { + await cp( + path.join(repoDir, "benchmark/toolmemory/gitcha.png"), + path.join(outputDir, language, "benchmarks", "gitcha.png"), + ); +} + +const defaultConfig = parseYaml(await readFile(path.join(repoDir, "reme/config/default.yaml"), "utf8")); +for (const language of ["zh", "en"]) { + const destination = path.join(outputDir, language, "reference", "jobs.md"); + await mkdir(path.dirname(destination), { recursive: true }); + await writeFile(destination, buildJobReference(defaultConfig, language)); + sourceMap[`${language}/reference/jobs.md`] = "reme/config/default.yaml"; +} + +await writeFile(path.join(outputDir, ".source-map.json"), `${JSON.stringify(sourceMap, null, 2)}\n`); diff --git a/github-pages/scripts/verify-build.mjs b/github-pages/scripts/verify-build.mjs new file mode 100644 index 00000000..a6e89e5c --- /dev/null +++ b/github-pages/scripts/verify-build.mjs @@ -0,0 +1,92 @@ +import assert from "node:assert/strict"; +import { access, readFile, readdir } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const siteDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const outputDir = path.join(siteDir, "dist"); + +async function collectFiles(directory, prefix = "") { + const files = []; + for (const entry of await readdir(directory, { withFileTypes: true })) { + const relativePath = path.posix.join(prefix, entry.name); + if (entry.isDirectory()) files.push(...await collectFiles(path.join(directory, entry.name), relativePath)); + else files.push(relativePath); + } + return files; +} + +function pageUrl(relativePath) { + if (relativePath === "index.html") return "/"; + if (relativePath.endsWith("/index.html")) return `/${relativePath.slice(0, -"index.html".length)}`; + return `/${relativePath.slice(0, -".html".length)}`; +} + +function routeExists(pathname, files) { + const relativePath = decodeURIComponent(pathname).replace(/^\/+/, ""); + if (!relativePath) return files.has("index.html"); + if (relativePath.endsWith("/")) return files.has(`${relativePath}index.html`); + return files.has(relativePath) || files.has(`${relativePath}.html`) || files.has(`${relativePath}/index.html`); +} + +const requiredFiles = [ + "index.html", + "404.html", + "CNAME", + "reme-icon.svg", + "reme-logo.svg", + "hashmap.json", + "sitemap.xml", + "llms.txt", + "llms-full.txt", + "zh/index.html", + "en/index.html", + "zh/configuration.html", + "en/configuration.html", + "zh/services.html", + "en/services.html", + "zh/reference/jobs.html", + "en/reference/jobs.html", + "zh/configuration/llms.txt", + "en/configuration/llms.txt", +]; + +for (const relativePath of requiredFiles) await access(path.join(outputDir, relativePath)); + +assert.equal((await readFile(path.join(outputDir, "CNAME"), "utf8")).trim(), "reme.agentscope.io"); + +const homepage = await readFile(path.join(outputDir, "index.html"), "utf8"); +assert.ok(homepage.includes('href="/en/"'), "root language switch must link to /en/"); +assert.ok(!homepage.includes('href="/en/ex"'), "root language switch must not produce /en/ex"); +assert.ok(homepage.includes('"studio-en":"/en/workspace/studio"'), "legacy redirects must be embedded"); + +const sitemap = await readFile(path.join(outputDir, "sitemap.xml"), "utf8"); +assert.ok(sitemap.includes(""), "sitemap must include canonical-source update times"); +assert.ok(!sitemap.includes("https://reme.agentscope.io/"), "root redirect must not be indexed"); +assert.ok(!sitemap.includes('hreflang="zh-CN" href="https://reme.agentscope.io/"'), "root must not duplicate zh-CN"); + +const ChineseConfiguration = await readFile(path.join(outputDir, "zh/configuration.html"), "utf8"); +assert.match(ChineseConfiguration, /搜索文档/); +assert.match(ChineseConfiguration, /复制 Markdown/); +assert.match(ChineseConfiguration, /在 GitHub 查看源文件/); + +const jobReference = await readFile(path.join(outputDir, "en/reference/jobs.html"), "utf8"); +assert.match(jobReference, /Job API Reference/); +assert.match(jobReference, /auto_memory/); + +const outputFiles = new Set(await collectFiles(outputDir)); +const missingLinks = []; +for (const relativePath of [...outputFiles].filter((file) => file.endsWith(".html"))) { + const html = await readFile(path.join(outputDir, relativePath), "utf8"); + const currentUrl = new URL(pageUrl(relativePath), "https://reme-docs.local"); + for (const match of html.matchAll(/]*\bhref="([^"]+)"/g)) { + const href = match[1].replaceAll("&", "&"); + if (href.startsWith("#")) continue; + const target = new URL(href, currentUrl); + if (target.origin !== currentUrl.origin) continue; + if (!routeExists(target.pathname, outputFiles)) missingLinks.push(`${relativePath}: ${href}`); + } +} +assert.deepEqual(missingLinks, [], `missing internal links:\n${missingLinks.join("\n")}`); + +console.log(`Verified ${requiredFiles.length} documentation build artifacts.`); diff --git a/github-pages/src/main.js b/github-pages/src/main.js deleted file mode 100644 index 9397ce41..00000000 --- a/github-pages/src/main.js +++ /dev/null @@ -1,458 +0,0 @@ -import DOMPurify from "dompurify"; -import { marked } from "marked"; -import { stripMarkdownFrontmatter } from "./markdown.js"; -import "./styles.css"; - -const baseUrl = import.meta.env.BASE_URL; -const repositoryUrl = "https://github.com/agentscope-ai/ReMe"; - -const copy = { - zh: { - docs: "文档", - home: "首页", - search: "搜索文档…", - noResults: "没有找到匹配的文档", - menu: "打开导航", - toc: "本页目录", - edit: "在 GitHub 查看源文件", - quickStart: "快速开始", - groups: { - overview: "项目介绍", - start: "开始使用", - fundamentals: "核心原理", - automation: "自动化能力", - concepts: "架构与场景", - integration: "Agent 集成", - workspace: "工作区", - plugins: "插件", - benchmarks: "评测", - development: "开发者规范", - }, - }, - en: { - docs: "Documentation", - home: "Home", - search: "Search documentation…", - noResults: "No matching documents", - menu: "Open navigation", - toc: "On this page", - edit: "View source on GitHub", - quickStart: "Quick start", - groups: { - overview: "Introduction", - start: "Get started", - fundamentals: "Fundamentals", - automation: "Automation", - concepts: "Architecture & scenarios", - integration: "Agent integration", - workspace: "Workspace", - plugins: "Plugins", - benchmarks: "Benchmarks", - development: "Development", - }, - }, -}; - -const state = { - language: localStorage.getItem("reme-docs-language") || "zh", - documents: [], - activeDocument: null, - query: "", -}; - -const homeCopy = { - zh: { - eyebrow: "LOCAL-FIRST · FILE-NATIVE", - title: "让 Agent 真正记住,\n也让记忆始终属于你。", - description: "ReMe 将对话和资料沉淀为可读、可编辑、可检索、相互链接的 Markdown,并提供从工作区管理到主动研究的一整套工具。", - start: "快速开始", - project: "了解 ReMe", - explore: "按目标探索", - exploreDescription: "选择你现在想完成的事情。每个入口都直接连接到对应的完整文档。", - cards: [ - { id: "studio-zh", icon: "◫", label: "管理记忆", title: "ReMe 工作台", description: "在本地 Web 工作区中浏览、编辑、搜索记忆,并探索 wikilink 图谱。", tone: "mint" }, - { id: "daily-paper-zh", icon: "◌", label: "发现与分析", title: "每日论文插件", description: "从论文榜单筛选值得阅读的工作,解析 PDF,并生成笔记与五分钟简报。", tone: "blue" }, - { id: "auto-fin-zh", icon: "↗", label: "主题研究", title: "Auto Fin 插件", description: "连接最新财联社新闻和本地历史记忆,生成可追溯的研究报告。", tone: "amber" }, - ], - benchmark: "验证记忆能力", - benchmarkDescription: "从检索规模、跨会话问答、个人智能体到工具经验,查看 ReMe 的四套评测与复现实验。", - benchmarkAction: "从 BEAM 开始", - }, - en: { - eyebrow: "LOCAL-FIRST · FILE-NATIVE", - title: "Memory that works for agents.\nFiles that remain yours.", - description: "ReMe turns conversations and resources into readable, editable, searchable, interconnected Markdown—with tools spanning workspace management and proactive research.", - start: "Quick start", - project: "Meet ReMe", - explore: "Explore by goal", - exploreDescription: "Start with what you want to accomplish. Every entry opens the complete guide.", - cards: [ - { id: "studio-en", icon: "◫", label: "Manage memory", title: "ReMe Studio", description: "Browse, edit, and search memory in a local web workspace, then explore its wikilink graph.", tone: "mint" }, - { id: "daily-paper-en", icon: "◌", label: "Discover & analyze", title: "Daily Paper Plugin", description: "Select useful papers from rankings, analyze PDFs, and create notes plus a five-minute brief.", tone: "blue" }, - { id: "auto-fin-en", icon: "↗", label: "Research topics", title: "Auto Fin Plugin", description: "Connect recent CLS news with historical local memory to produce traceable research reports.", tone: "amber" }, - ], - benchmark: "Validate memory systems", - benchmarkDescription: "Explore four reproducible evaluations covering retrieval scale, cross-session QA, personal agents, and tool-use experience.", - benchmarkAction: "Start with BEAM", - }, -}; - -const app = document.querySelector("#app"); - -app.innerHTML = ` -
- - R - ReMe - - - - -
-
- -
-
-
- -
- -`; - -const sidebar = app.querySelector(".sidebar"); -const docsShell = app.querySelector(".docs-shell"); -const documentNav = app.querySelector(".document-nav"); -const article = app.querySelector(".article"); -const toc = app.querySelector(".toc"); -const searchInput = app.querySelector("input[type='search']"); -const menuButton = app.querySelector(".menu-button"); -const backdrop = app.querySelector(".sidebar-backdrop"); - -function slugify(value) { - return value - .toLowerCase() - .trim() - .replace(/<[^>]+>/g, "") - .replace(/[^\p{Letter}\p{Number}]+/gu, "-") - .replace(/^-|-$/g, ""); -} - -function resolveDocumentPath(currentPath, target) { - const cleanTarget = target.split("#")[0].split("?")[0]; - const currentParts = currentPath.split("/"); - currentParts.pop(); - for (const part of cleanTarget.split("/")) { - if (!part || part === ".") continue; - if (part === "..") currentParts.pop(); - else currentParts.push(part); - } - return currentParts.join("/"); -} - -function configureMarkdown(document) { - const renderer = new marked.Renderer(); - const headingIds = new Map(); - - renderer.heading = ({ tokens, depth }) => { - const text = tokens.map((token) => token.text || token.raw || "").join(""); - const baseSlug = slugify(text) || "section"; - const count = headingIds.get(baseSlug) || 0; - headingIds.set(baseSlug, count + 1); - const id = count ? `${baseSlug}-${count + 1}` : baseSlug; - return `${text}`; - }; - - renderer.image = ({ href, title, text }) => { - const url = /^(https?:|data:)/.test(href) - ? href - : `${baseUrl}content/${resolveDocumentPath(document.path, href)}`; - const titleAttribute = title ? ` title="${title}"` : ""; - return `${text}`; - }; - - renderer.link = ({ href, title, tokens }) => { - const label = tokens.map((token) => token.text || token.raw || "").join(""); - const titleAttribute = title ? ` title="${title}"` : ""; - if (href.startsWith("#")) return `${label}`; - if (!/^(https?:|mailto:)/.test(href)) { - const resolved = resolveDocumentPath(document.path, href); - const localDocument = state.documents.find((item) => item.path === resolved); - if (localDocument) return `${label}`; - return `${label}`; - } - return `${label}`; - }; - - marked.use({ renderer, gfm: true, breaks: false }); -} - -function availableDocuments() { - return state.documents.filter( - (document) => document.language === state.language || document.language === "shared", - ); -} - -function documentTitle(document) { - return document.titles?.[state.language] || document.title; -} - -function renderChrome() { - const labels = copy[state.language]; - app.querySelector("[data-copy='docs']").textContent = labels.docs; - const quickStartLink = app.querySelector("[data-copy='quickStart']"); - quickStartLink.textContent = `${labels.quickStart} →`; - quickStartLink.href = `?doc=${state.language}-quick_start`; - quickStartLink.dataset.doc = `${state.language}-quick_start`; - searchInput.placeholder = labels.search; - menuButton.textContent = labels.menu; - document.documentElement.lang = state.language === "zh" ? "zh-CN" : "en"; - app.querySelectorAll("[data-language]").forEach((button) => { - button.classList.toggle("active", button.dataset.language === state.language); - }); -} - -function renderNavigation() { - const labels = copy[state.language]; - const query = state.query.trim().toLocaleLowerCase(); - const filtered = availableDocuments().filter((document) => - `${documentTitle(document)} ${document.title || ""} ${document.description}`.toLocaleLowerCase().includes(query), - ); - const groups = [...new Set(filtered.map((document) => document.group))]; - - if (!filtered.length) { - documentNav.innerHTML = ` - ${labels.home} -

${labels.noResults}

- `; - return; - } - - documentNav.innerHTML = ` - ${labels.home} - ` + groups - .map( - (group) => ` - `, - ) - .join(""); -} - -function renderHome(pushHistory = true) { - const labels = homeCopy[state.language]; - state.activeDocument = null; - docsShell.classList.add("home-view"); - article.dataset.group = "home"; - renderNavigation(); - article.innerHTML = ` -
-

${labels.eyebrow}

-

${labels.title.replace("\n", "
")}

-

${labels.description}

- -
-
-

01 / PRODUCT & PLUGINS

-

${labels.explore}

-

${labels.exploreDescription}

- -
-
-
-

02 / BENCHMARKS

-

${labels.benchmark}

-

${labels.benchmarkDescription}

-
- ${labels.benchmarkAction} → -
- `; - toc.innerHTML = ""; - closeMenu(); - if (pushHistory) history.pushState({ home: true }, "", baseUrl); - window.scrollTo({ top: 0, behavior: "instant" }); -} - -function renderToc() { - const headings = [...article.querySelectorAll("h2, h3")]; - if (!headings.length) { - toc.innerHTML = ""; - return; - } - toc.innerHTML = ` -

${copy[state.language].toc}

- ${headings - .map( - (heading) => `${heading.childNodes[0]?.textContent || heading.textContent}`, - ) - .join("")} - `; -} - -function rewriteRenderedUrls(document) { - article.querySelectorAll("img[src]").forEach((image) => { - const source = image.getAttribute("src"); - if (source && !/^(https?:|data:|\/)/.test(source)) { - image.src = `${baseUrl}content/${resolveDocumentPath(document.path, source)}`; - } - }); - - article.querySelectorAll("a[href]").forEach((link) => { - const href = link.getAttribute("href"); - if (!href || /^(https?:|mailto:|#|\/)/.test(href) || link.dataset.doc) return; - const resolved = resolveDocumentPath(document.path, href); - const localDocument = state.documents.find((item) => item.path === resolved); - if (localDocument) { - link.href = `?doc=${localDocument.id}`; - link.dataset.doc = localDocument.id; - link.removeAttribute("target"); - return; - } - link.href = `${repositoryUrl}/blob/main/${resolved}`; - link.target = "_blank"; - link.rel = "noreferrer"; - }); -} - -async function openDocument(id, pushHistory = true) { - const fallbackId = state.language === "zh" ? "readme-zh" : "readme-en"; - const document = state.documents.find((item) => item.id === id) || state.documents.find((item) => item.id === fallbackId); - if (document.language !== "shared" && document.language !== state.language) { - state.language = document.language; - localStorage.setItem("reme-docs-language", state.language); - renderChrome(); - } - state.activeDocument = document; - docsShell.classList.remove("home-view"); - article.dataset.group = document.group; - renderNavigation(); - article.innerHTML = `
`; - - const response = await fetch(`${baseUrl}content/${document.path}`); - if (!response.ok) throw new Error(`Unable to load ${document.path}`); - configureMarkdown(document); - const markdown = stripMarkdownFrontmatter(await response.text()); - const body = DOMPurify.sanitize(await marked.parse(markdown), { - ADD_ATTR: ["target"], - }); - article.innerHTML = ` - -
${body}
- - `; - rewriteRenderedUrls(document); - renderToc(); - closeMenu(); - if (pushHistory) history.pushState({ doc: document.id }, "", `?doc=${document.id}`); - window.scrollTo({ top: 0, behavior: "instant" }); -} - -function closeMenu() { - sidebar.classList.remove("open"); - backdrop.classList.remove("visible"); - menuButton.setAttribute("aria-expanded", "false"); -} - -function toggleMenu() { - const open = !sidebar.classList.contains("open"); - sidebar.classList.toggle("open", open); - backdrop.classList.toggle("visible", open); - menuButton.setAttribute("aria-expanded", String(open)); -} - -app.addEventListener("click", (event) => { - const homeLink = event.target.closest("[data-home]"); - if (homeLink) { - event.preventDefault(); - renderHome(); - return; - } - const documentLink = event.target.closest("[data-doc]"); - if (documentLink) { - event.preventDefault(); - openDocument(documentLink.dataset.doc); - } - if (event.target.closest("[data-action='menu']")) toggleMenu(); - const languageButton = event.target.closest("[data-language]"); - if (languageButton && languageButton.dataset.language !== state.language) { - state.language = languageButton.dataset.language; - localStorage.setItem("reme-docs-language", state.language); - state.query = ""; - searchInput.value = ""; - renderChrome(); - renderHome(); - } -}); - -searchInput.addEventListener("input", () => { - state.query = searchInput.value; - renderNavigation(); -}); - -document.addEventListener("keydown", (event) => { - if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") { - event.preventDefault(); - searchInput.focus(); - } - if (event.key === "Escape") closeMenu(); -}); - -backdrop.addEventListener("click", closeMenu); -window.addEventListener("popstate", (event) => { - const id = event.state?.doc || new URLSearchParams(location.search).get("doc"); - if (id) openDocument(id, false); - else renderHome(false); -}); - -const manifest = await fetch(`${baseUrl}content/manifest.json`).then((response) => response.json()); -state.documents = manifest.documents; -renderChrome(); -const initialDocument = new URLSearchParams(location.search).get("doc"); -if (initialDocument) await openDocument(initialDocument, false); -else renderHome(false); diff --git a/github-pages/src/markdown.js b/github-pages/src/markdown.js deleted file mode 100644 index ef81a773..00000000 --- a/github-pages/src/markdown.js +++ /dev/null @@ -1,6 +0,0 @@ -const FRONTMATTER_PATTERN = /^\uFEFF?---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/; - -/** Remove a leading YAML frontmatter block before rendering Markdown. */ -export function stripMarkdownFrontmatter(markdown) { - return markdown.replace(FRONTMATTER_PATTERN, ""); -} diff --git a/github-pages/src/styles.css b/github-pages/src/styles.css deleted file mode 100644 index 7b3ee4ad..00000000 --- a/github-pages/src/styles.css +++ /dev/null @@ -1,200 +0,0 @@ -:root { - color: #17221d; - background: #f4f7f5; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-synthesis: none; - text-rendering: optimizeLegibility; - --ink: #17221d; - --muted: #65716a; - --line: #dce5e0; - --paper: #ffffff; - --green: #087f6a; - --blue: #3156d9; - --green-soft: #e5f5ef; - --code: #f3f5f2; -} - -* { box-sizing: border-box; } -html { scroll-behavior: smooth; scroll-padding-top: 92px; } -body { - margin: 0; - min-width: 320px; - background: - radial-gradient(circle at 8% 9%, rgba(27, 193, 164, 0.06), transparent 26rem), - #f4f7f5; -} -button, input { font: inherit; } -a { color: inherit; } - -.topbar { - position: fixed; - inset: 0 0 auto 0; - z-index: 30; - height: 66px; - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 28px; - border-bottom: 1px solid rgba(213, 225, 219, 0.85); - background: rgba(250, 252, 251, 0.88); - backdrop-filter: blur(18px) saturate(140%); -} - -.brand { display: flex; align-items: center; gap: 11px; color: var(--ink); text-decoration: none; font-weight: 760; } -.brand-mark { - display: grid; - place-items: center; - width: 34px; - height: 34px; - border-radius: 11px; - color: white; - background: linear-gradient(145deg, #19c9b0, #3156d9 82%); - box-shadow: 0 7px 18px rgba(24, 123, 114, 0.2); - font-family: Georgia, serif; - font-size: 21px; -} -.brand-divider { width: 1px; height: 20px; background: var(--line); margin-left: 2px; } -.brand-section { color: var(--muted); font-weight: 520; } -.top-actions { display: flex; align-items: center; gap: 18px; } -.github-link, .quick-start-link { color: #37443d; text-decoration: none; font-size: 13px; font-weight: 650; } -.github-link:hover, .quick-start-link:hover { color: var(--green); } -.quick-start-link { padding: 8px 12px; border: 1px solid #d7e5df; border-radius: 9px; background: rgba(255, 255, 255, 0.72); } -.language-switch { display: flex; padding: 3px; border: 1px solid var(--line); border-radius: 9px; background: #f5f7f4; } -.language-switch button { padding: 5px 9px; border: 0; border-radius: 6px; color: var(--muted); background: transparent; cursor: pointer; font-size: 12px; font-weight: 700; } -.language-switch button.active { color: var(--ink); background: white; box-shadow: 0 1px 3px rgba(20, 40, 30, 0.1); } -.menu-button { display: none; border: 1px solid var(--line); border-radius: 8px; background: white; padding: 7px 10px; cursor: pointer; } - -.docs-shell { display: grid; grid-template-columns: 276px minmax(0, 1fr) 224px; max-width: 1540px; min-height: 100vh; margin: 0 auto; padding-top: 66px; } -.docs-shell.home-view { grid-template-columns: 276px minmax(0, 1fr); } -.docs-shell.home-view .toc-panel { display: none; } -.sidebar { position: sticky; top: 66px; height: calc(100vh - 66px); padding: 25px 20px 18px; overflow-y: auto; border-right: 1px solid var(--line); background: rgba(247, 250, 248, 0.78); } -.search-box { display: flex; align-items: center; gap: 8px; height: 41px; padding: 0 11px; border: 1px solid #d8e3dd; border-radius: 11px; color: #7a857e; background: rgba(255, 255, 255, 0.84); box-shadow: 0 5px 18px rgba(29, 65, 48, 0.035); } -.search-box:focus-within { border-color: #78aa8e; box-shadow: 0 0 0 3px rgba(22, 120, 76, 0.1); } -.search-box input { width: 100%; border: 0; outline: 0; color: var(--ink); background: transparent; font-size: 13px; } -.search-box kbd { padding: 2px 5px; border: 1px solid var(--line); border-radius: 4px; background: #f7f8f6; font-size: 10px; } -.document-nav { padding: 12px 0 52px; } -.home-link { position: relative; display: block; margin-top: 9px; padding: 9px 10px 9px 13px; border-radius: 8px; color: #536058; text-decoration: none; font-size: 13px; } -.home-link:hover { color: var(--ink); background: #f0f3ef; } -.home-link.active { color: #086b5a; background: linear-gradient(90deg, #dff3ec, #eaf6f2); font-weight: 700; } -.nav-group { margin-top: 21px; } -.nav-group h2 { margin: 0 10px 7px; color: #8a948e; font-size: 10px; font-weight: 800; letter-spacing: 0.11em; text-transform: uppercase; } -.nav-group a { position: relative; display: block; padding: 7px 10px 7px 13px; border-radius: 8px; color: #536058; text-decoration: none; font-size: 13px; line-height: 1.4; } -.nav-group a:hover { color: var(--ink); background: #f0f3ef; } -.nav-group a.active { color: #086b5a; background: linear-gradient(90deg, #dff3ec, #eaf6f2); font-weight: 700; } -.nav-group a.active::before { position: absolute; top: 9px; bottom: 9px; left: 0; width: 3px; border-radius: 3px; background: linear-gradient(#17b79d, #3470d8); content: ""; } -.empty-state { padding: 24px 10px; color: var(--muted); font-size: 13px; } -.sidebar-footer { position: sticky; bottom: -18px; display: flex; align-items: center; gap: 8px; margin: 0 -20px; padding: 14px 22px 18px; border-top: 1px solid var(--line); color: #7c8880; background: #f7faf8; font: 600 10px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.04em; text-transform: uppercase; } -.status-dot { width: 6px; height: 6px; border-radius: 50%; background: #29a869; box-shadow: 0 0 0 3px #dff3e8; } - -.article-wrap { min-width: 0; padding: 58px clamp(32px, 5.8vw, 86px) 100px; background: rgba(255, 255, 255, 0.94); } -.article { width: 100%; max-width: 820px; margin: 0 auto; } -.article[data-group="home"] { max-width: 980px; } -.home-hero { padding: 34px 0 76px; } -.home-eyebrow, .section-kicker { margin: 0 0 17px; color: #12806d; font: 750 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.13em; } -.home-hero h1 { margin: 0; color: #102019; font-size: clamp(46px, 6.3vw, 76px); line-height: 1.15; letter-spacing: -0.055em; } -.home-lead { margin: 27px 0 0; color: #526159; font-size: 18px; line-height: 1.72; } -.home-actions { display: flex; flex-wrap: wrap; gap: 11px; margin-top: 31px; } -.home-actions a { padding: 11px 17px; border-radius: 10px; text-decoration: none; font-size: 14px; font-weight: 720; } -.primary-action { color: white; background: #087f6a; box-shadow: 0 8px 22px rgba(8, 127, 106, 0.2); } -.secondary-action { border: 1px solid #d5e2dc; color: #34443c; background: white; } -.home-explore { padding-top: 58px; border-top: 1px solid var(--line); } -.home-explore h2, .benchmark-callout h2 { margin: 0; color: #15251d; font-size: 30px; letter-spacing: -0.025em; } -.section-lead { max-width: 620px; margin: 10px 0 25px; color: var(--muted); line-height: 1.65; } -.feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; } -.feature-card { position: relative; display: flex; min-height: 260px; flex-direction: column; padding: 23px; overflow: hidden; border: 1px solid #dce8e2; border-radius: 16px; color: var(--ink); background: linear-gradient(155deg, #fff, #f4faf7); text-decoration: none; transition: transform 160ms ease, box-shadow 160ms ease; } -.feature-card:hover { transform: translateY(-3px); box-shadow: 0 16px 34px rgba(29, 68, 49, 0.1); } -.feature-card.blue { background: linear-gradient(155deg, #fff, #f1f5ff); } -.feature-card.amber { background: linear-gradient(155deg, #fff, #fbf7ec); } -.feature-icon { display: grid; place-items: center; width: 42px; height: 42px; margin-bottom: 32px; border-radius: 12px; color: #08705e; background: #dbf2ea; font-size: 22px; } -.blue .feature-icon { color: #3156b8; background: #e5ebff; } -.amber .feature-icon { color: #9b6818; background: #f8eac8; } -.feature-label { margin-bottom: 7px; color: #718078; font: 700 10px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.08em; text-transform: uppercase; } -.feature-card strong { font-size: 20px; } -.feature-description { margin-top: 10px; color: #66736c; font-size: 13px; line-height: 1.6; } -.feature-arrow { position: absolute; right: 22px; bottom: 18px; color: #6a7870; font-size: 19px; } -.benchmark-callout { display: flex; align-items: end; justify-content: space-between; gap: 35px; margin-top: 62px; padding: 36px; border-radius: 17px; color: white; background: linear-gradient(125deg, #142a22, #1c473b); } -.benchmark-callout .section-kicker { color: #74d4bb; } -.benchmark-callout h2 { color: white; } -.benchmark-callout p:not(.section-kicker) { max-width: 610px; margin: 10px 0 0; color: #c3d4cd; line-height: 1.65; } -.benchmark-callout > a { flex: none; padding: 10px 14px; border: 1px solid rgba(255,255,255,.25); border-radius: 9px; color: white; text-decoration: none; font-size: 13px; font-weight: 700; } -.article-meta { display: flex; gap: 8px; margin-bottom: 22px; color: #7d8d84; font: 600 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; } -.article-meta span:first-child { padding: 3px 8px; border-radius: 999px; color: #08705e; background: #e6f5ef; } -.markdown-body { color: #27332d; font-size: 16px; line-height: 1.78; } -.markdown-body > :first-child { margin-top: 0; } -.markdown-body h1 { margin: 0 0 30px; color: #112019; font-size: clamp(36px, 5vw, 54px); line-height: 1.06; letter-spacing: -0.04em; } -.markdown-body h2 { margin: 58px 0 18px; padding-top: 4px; color: #14241c; font-size: 27px; line-height: 1.25; letter-spacing: -0.02em; } -.markdown-body h3 { margin: 35px 0 12px; color: #1f3027; font-size: 20px; line-height: 1.35; } -.markdown-body p, .markdown-body ul, .markdown-body ol { margin: 14px 0; } -.markdown-body li { margin: 5px 0; } -.markdown-body a { color: var(--green); text-decoration-color: #9cc8ae; text-underline-offset: 3px; } -.markdown-body a:hover { text-decoration-color: var(--green); } -.markdown-body img { display: block; max-width: 100%; height: auto; margin: 30px auto; border-radius: 14px; } -/* README badges are linked images. Keep them in a compact row instead of applying the figure layout above. */ -.article[data-group="overview"] .markdown-body > p:has(> a > img[src*="img.shields.io"]) { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 7px; - max-width: 690px; - margin: 20px auto; -} -.article[data-group="overview"] .markdown-body > p > a > img[src*="img.shields.io"] { - display: inline-block; - width: auto; - max-height: 24px; - margin: 0; - border-radius: 4px; - vertical-align: middle; -} -.markdown-body p[align="center"] { text-align: center; } -.article[data-group="overview"] .markdown-body > p:first-child img { max-width: min(470px, 72%); margin-top: 8px; filter: drop-shadow(0 18px 25px rgba(44, 85, 164, 0.1)); } -.markdown-body code { padding: 2px 5px; border-radius: 5px; background: var(--code); color: #315844; font: 0.88em/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; } -.markdown-body pre { margin: 24px 0; padding: 19px 21px; overflow-x: auto; border: 1px solid #dce5e0; border-radius: 12px; background: linear-gradient(145deg, #f5f8f6, #f8faf9); box-shadow: inset 3px 0 0 #7bd0b6; } -.markdown-body pre code { padding: 0; color: #263b30; background: none; font-size: 13px; } -.markdown-body blockquote { margin: 24px 0; padding: 4px 20px; border-left: 3px solid #68aa85; color: #53635a; background: #f5faf7; } -.markdown-body table { display: block; width: 100%; margin: 24px 0; overflow-x: auto; border-collapse: collapse; font-size: 14px; } -.markdown-body th, .markdown-body td { padding: 10px 13px; border: 1px solid var(--line); text-align: left; } -.markdown-body th { background: #f4f6f3; } -.markdown-body hr { margin: 44px 0; border: 0; border-top: 1px solid var(--line); } -.article-footer { margin-top: 70px; padding-top: 20px; border-top: 1px solid var(--line); } -.article-footer a { color: var(--muted); text-decoration: none; font-size: 13px; } -.article-footer a:hover { color: var(--green); } -.loading-line { width: 55%; height: 12px; margin-top: 40px; border-radius: 9px; background: linear-gradient(90deg, #edf0ec, #f8faf7, #edf0ec); background-size: 200% 100%; animation: loading 1.2s infinite; } -@keyframes loading { to { background-position: -200% 0; } } - -.toc-panel { position: sticky; top: 66px; height: calc(100vh - 66px); padding: 58px 24px; overflow-y: auto; border-left: 1px solid #e9efeb; background: rgba(252, 253, 252, 0.92); } -.toc h2 { margin: 0 0 12px; color: #8a948e; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; } -.toc a { display: block; padding: 5px 0; color: #7a857e; text-decoration: none; font-size: 12px; line-height: 1.45; } -.toc a:hover { color: var(--green); } -.toc .toc-h3 { padding-left: 12px; } -.sidebar-backdrop { display: none; } - -@media (max-width: 1120px) { - .docs-shell { grid-template-columns: 250px minmax(0, 1fr); } - .toc-panel { display: none; } -} - -@media (max-width: 760px) { - .topbar { height: 60px; padding: 0 16px; } - .brand-section, .brand-divider, .github-link { display: none; } - .quick-start-link { font-size: 12px; } - .menu-button { display: block; max-width: 112px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .top-actions { gap: 9px; } - .docs-shell { display: block; padding-top: 60px; } - .sidebar { position: fixed; z-index: 25; top: 60px; bottom: 0; left: 0; width: min(310px, 86vw); height: auto; transform: translateX(-105%); transition: transform 180ms ease; box-shadow: 16px 0 35px rgba(20, 40, 30, 0.12); } - .sidebar.open { transform: translateX(0); } - .sidebar-backdrop { position: fixed; z-index: 20; inset: 60px 0 0; width: 100%; border: 0; background: rgba(18, 30, 23, 0.35); } - .sidebar-backdrop.visible { display: block; } - .article-wrap { padding: 38px 20px 72px; } - .article-meta { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } - .markdown-body { font-size: 15px; } - .markdown-body h1 { font-size: 34px; } - .markdown-body h2 { margin-top: 46px; font-size: 24px; } - .article[data-group="overview"] .markdown-body > p:first-child img { max-width: 84%; } - .home-hero { padding: 20px 0 54px; } - .home-hero h1 { font-size: 43px; } - .home-lead { font-size: 16px; } - .feature-grid { grid-template-columns: 1fr; } - .feature-card { min-height: 225px; } - .benchmark-callout { align-items: flex-start; flex-direction: column; padding: 28px 24px; } -} diff --git a/github-pages/tests/content-manifest.test.mjs b/github-pages/tests/content-manifest.test.mjs deleted file mode 100644 index bc1a8a0b..00000000 --- a/github-pages/tests/content-manifest.test.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import assert from "node:assert/strict"; -import { readFile } from "node:fs/promises"; -import test from "node:test"; - -const manifestUrl = new URL("../.generated/content/manifest.json", import.meta.url); - -test("omits retired Agent documents and places Agent integration after getting started", async () => { - const manifest = JSON.parse(await readFile(manifestUrl, "utf8")); - const documents = manifest.documents; - const groups = [...new Set(documents.map((document) => document.group))]; - - assert.equal(documents.some((document) => document.id === "reme-memory-skill"), false); - assert.equal(documents.some((document) => document.sourcePath.endsWith("agent_integration_plan.md")), false); - assert.deepEqual( - documents - .filter((document) => document.group === "plugins") - .map((document) => document.title || document.titles?.en), - ["每日论文插件", "Auto Fin 插件", "Daily Paper Plugin", "Auto Fin Plugin"], - ); - assert.equal(documents.some((document) => document.group === "cookbooks"), false); - assert.ok(groups.indexOf("integration") > groups.indexOf("start")); - assert.ok(groups.indexOf("integration") < groups.indexOf("fundamentals")); -}); diff --git a/github-pages/tests/generated-content.test.mjs b/github-pages/tests/generated-content.test.mjs new file mode 100644 index 00000000..6aceeaee --- /dev/null +++ b/github-pages/tests/generated-content.test.mjs @@ -0,0 +1,109 @@ +import assert from "node:assert/strict"; +import { access, readFile } from "node:fs/promises"; +import path from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; +import { parse as parseYaml } from "yaml"; +import { legacyRoutes } from "../../docs/.vitepress/legacy-routes.mjs"; + +const siteDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const repoDir = path.resolve(siteDir, ".."); +const generatedDir = path.join(siteDir, ".generated", "site"); + +test("generates every required bilingual guide", async () => { + const names = [ + "configuration.md", + "services.md", + "operations.md", + "integrations.md", + "plugin_development.md", + "faq.md", + "reference/cli.md", + "reference/jobs.md", + ]; + for (const language of ["zh", "en"]) { + for (const name of names) await access(path.join(generatedDir, language, name)); + } + await access(path.join(generatedDir, "zh/integrations/claude-code.md")); + await access(path.join(generatedDir, "en/integrations/claude-code.md")); + await access(path.join(generatedDir, "zh/integrations/hermes.md")); + await access(path.join(generatedDir, "en/integrations/hermes.md")); + await access(path.join(generatedDir, "zh/integrations/dsh.md")); + await access(path.join(generatedDir, "en/integrations/dsh.md")); + await access(path.join(generatedDir, "zh/integrations/openclaw.md")); + await access(path.join(generatedDir, "en/integrations/openclaw.md")); + await access(path.join(generatedDir, "public/figures/dsh/reme-status-overview.png")); +}); + +test("maps mirrored pages back to their canonical repository sources", async () => { + const sourceMap = JSON.parse(await readFile(path.join(generatedDir, ".source-map.json"), "utf8")); + assert.equal(sourceMap["zh/integrations/typescript.md"], "typescript/README_ZH.md"); + assert.equal(sourceMap["en/integrations/dsh.md"], "typescript/docs/dsh.md"); + assert.equal(sourceMap["zh/integrations/openclaw.md"], "typescript/docs/openclaw.zh-CN.md"); + assert.equal(sourceMap["en/integrations/claude-code.md"], "integrations/claude_code/README.md"); + assert.equal(sourceMap["en/integrations/hermes.md"], "integrations/hermes_agent/README.md"); + assert.equal(sourceMap["en/workspace/studio.md"], "reme_studio/README.md"); + assert.equal(sourceMap["zh/plugins/lme.md"], "plugins/lme/README_ZH.md"); + assert.equal(sourceMap["en/reference/jobs.md"], "reme/config/default.yaml"); +}); + +test("publishes portable and accurate DSH instructions", async () => { + const english = await readFile(path.join(generatedDir, "en/integrations/dsh.md"), "utf8"); + const chinese = await readFile(path.join(generatedDir, "zh/integrations/dsh.md"), "utf8"); + assert.doesNotMatch(english, /\/Users\//); + assert.doesNotMatch(chinese, /\/Users\//); + assert.match(english, /runtime counters refresh every 5 seconds/); + assert.match(chinese, /每 5 秒仅刷新 DSH 插件的运行时计数/); +}); + +test("generates the callable Job reference from default.yaml", async () => { + const config = parseYaml(await readFile(path.join(repoDir, "reme/config/default.yaml"), "utf8")); + const callableJobs = Object.entries(config.jobs) + .filter(([, job]) => !["background", "cron"].includes(job.backend)) + .map(([name]) => name); + + for (const language of ["zh", "en"]) { + const reference = await readFile(path.join(generatedDir, language, "reference", "jobs.md"), "utf8"); + for (const job of callableJobs) assert.ok(reference.includes(`### ${"`"}${job}${"`"}`), job); + } +}); + +test("keeps generated content disposable and excludes internal plans", async () => { + await assert.rejects(access(path.join(generatedDir, "plans"))); + await access(path.join(generatedDir, ".vitepress", "config.mts")); + await access(path.join(generatedDir, "public", "reme-icon.svg")); + await access(path.join(generatedDir, "public", "reme-logo.svg")); + assert.equal( + (await readFile(path.join(generatedDir, "public", "CNAME"), "utf8")).trim(), + "reme.agentscope.io", + ); +}); + +test("maps every legacy query-string document ID to a generated page", async () => { + assert.equal(Object.keys(legacyRoutes).length, 45); + assert.equal(legacyRoutes["studio-en"], "/en/workspace/studio"); + assert.equal(legacyRoutes["en-quick_start"], "/en/quick_start"); + assert.equal(legacyRoutes["agents-guide"], "https://github.com/agentscope-ai/ReMe/blob/main/AGENTS.md"); + + for (const [id, route] of Object.entries(legacyRoutes)) { + if (route.startsWith("https://")) continue; + assert.match(route, /^\/(?:zh|en)\//, id); + const relative = route.endsWith("/") ? `${route.slice(1)}index.md` : `${route.slice(1)}.md`; + await access(path.join(generatedDir, relative)); + } +}); + +test("tracks every generated input in documentation CI and deployment", async () => { + const requiredPaths = [ + "reme/config/default.yaml", + "integrations/claude_code/README.md", + "integrations/hermes_agent/README.md", + "typescript/docs/**", + "typescript/figures/**", + "benchmark/toolmemory/gitcha.png", + ]; + for (const workflow of ["ci-docs.yml", "deploy-docs.yml"]) { + const source = await readFile(path.join(repoDir, ".github/workflows", workflow), "utf8"); + for (const requiredPath of requiredPaths) assert.ok(source.includes(requiredPath), `${workflow}: ${requiredPath}`); + } +}); diff --git a/github-pages/tests/markdown.test.mjs b/github-pages/tests/markdown.test.mjs deleted file mode 100644 index 129b6190..00000000 --- a/github-pages/tests/markdown.test.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import assert from "node:assert/strict"; -import test from "node:test"; -import { stripMarkdownFrontmatter } from "../src/markdown.js"; - -test("strips leading YAML frontmatter before rendering", () => { - assert.equal( - stripMarkdownFrontmatter("---\nname: reme_memory\ndescription: Memory skill\n---\n\n# ReMe Memory\n"), - "\n# ReMe Memory\n", - ); -}); - -test("preserves Markdown without frontmatter", () => { - const markdown = "# ReMe Memory\n\nContent\n"; - assert.equal(stripMarkdownFrontmatter(markdown), markdown); -}); diff --git a/github-pages/vite.config.js b/github-pages/vite.config.js deleted file mode 100644 index 1445f872..00000000 --- a/github-pages/vite.config.js +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "vite"; - -export default defineConfig({ - base: "./", - publicDir: ".generated", - build: { - outDir: "dist", - emptyOutDir: true, - }, -}); diff --git a/plugins/beam/LICENSE b/plugins/beam/LICENSE new file mode 100644 index 00000000..65c2c5cf --- /dev/null +++ b/plugins/beam/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 Alibaba Group + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/beam/README.md b/plugins/beam/README.md new file mode 100644 index 00000000..fb2f9559 --- /dev/null +++ b/plugins/beam/README.md @@ -0,0 +1,38 @@ +# BEAM plugin + +[中文说明](./README_ZH.md) + +This plugin owns the BEAM memory, agentic-answer and judge Steps, their prompts, +and their Job defaults in `plugin.yaml`. ReMe's built-in `benchmark.yaml` owns the +shared evaluation Jobs and components. Dataset handling, the runner and results +remain in [`benchmark/beam`](../../benchmark/beam/README.md). + +From the repository root, install ReMe and this plugin in editable mode before running the benchmark: + +```bash +python -m pip install -e ".[as]" +reme plugins install ./plugins/beam --editable +reme plugins validate beam +python benchmark/beam/run.py +``` + +Editable installation registers the `beam` entry point while keeping source changes immediately +visible. The runner selects the built-in `benchmark` preset and explicitly enables `beam` for +each Application. Installing the plugin makes it discoverable but does not enable it globally. + +`plugin.yaml` registers backends and contributes the plugin-owned `auto_memory`, +`agentic_answer` and `answer_judge` Job defaults. Start the installed plugin with +`reme start config=benchmark plugins='["beam"]'`. The shared preset does not inherit +`default`: only declared Jobs run, indexing is manual, and neither scheduled dream +nor the optional `auto_dream` Job is enabled. +The existing `auto_memory`, `agentic_answer`, `answer_judge`, `bench` and `judge` +names and model environment variables are unchanged. Explicit application/CLI overrides +still take precedence. Installing this plugin does not start an evaluation. + +The shared answer base class lives in `reme.steps.benchmark.base_agentic_answer`. +The old core-owned `reme.steps.benchmark.beam` Python import path is removed. +Custom Python callers should import memory, search and answer Steps from `reme_beam`, and the +judge Step from `judge_beam`. After uninstalling, +Applications and CLI services must omit the plugin until it is installed again. +Uninstallation never removes datasets, workspaces or results. +Restart an existing service after changing plugins. diff --git a/plugins/beam/README_ZH.md b/plugins/beam/README_ZH.md new file mode 100644 index 00000000..4d695715 --- /dev/null +++ b/plugins/beam/README_ZH.md @@ -0,0 +1,32 @@ +# BEAM 插件 + +[English](./README.md) + +插件包含 BEAM 的记忆、回答、评分 Step、提示词,以及 `plugin.yaml` 中对应的 Job 默认配置。 +ReMe 内置的 `benchmark.yaml` 负责公共评测 Job 和 Component;数据集处理、runner 和结果仍留在 +[`benchmark/beam`](../../benchmark/beam/README_ZH.md)。 + +在仓库根目录以 editable 模式安装 ReMe 和本插件,再运行评测: + +```bash +python -m pip install -e ".[as]" +reme plugins install ./plugins/beam --editable +reme plugins validate beam +python benchmark/beam/run.py +``` + +editable 安装会注册 `beam` entry point,并让源码修改立即生效。runner 选择内置 `benchmark` +配置,并为每个 Application 显式启用 `beam`。安装只让插件可被发现,不会在所有应用中全局启用。 + +`plugin.yaml` 注册 backend,并通过 `application_defaults` 提供插件拥有的 `auto_memory`、 +`agentic_answer` 和 `answer_judge` Job。安装后使用 +`reme start config=benchmark plugins='["beam"]'`。公共评测配置不继承 `default`,只运行声明的 Job: +索引手动更新,dream 定时任务和可选的 `auto_dream` +均保持关闭。原有 `auto_memory`、`agentic_answer`、`answer_judge`、`bench`、`judge` 名称及模型环境变量 +保持不变,显式应用参数和 CLI 覆盖仍优先。安装或启用插件不会自动开始评测。 + +共享回答基类位于 `reme.steps.benchmark.base_agentic_answer`。 +原 `reme.steps.benchmark.beam` Python 导入路径已移除。自定义 Python 调用应从 `reme_beam` +导入记忆、搜索和回答 Step,并从 `judge_beam` 导入评判 Step。 +卸载插件后,Application 和 CLI 服务必须移除插件选择,直到再次安装。 +卸载不会删除数据集、工作区或结果。修改插件后需重启已有服务。 diff --git a/plugins/beam/pyproject.toml b/plugins/beam/pyproject.toml new file mode 100644 index 00000000..2b35f5aa --- /dev/null +++ b/plugins/beam/pyproject.toml @@ -0,0 +1,28 @@ +[project] +name = "reme-beam" +version = "0.1.0" +description = "BEAM benchmark plugin for ReMe." +readme = "README.md" +license = "Apache-2.0" +license-files = ["LICENSE"] +requires-python = ">=3.11" +dependencies = [ + "reme-ai[as]>=0.4.1.11", + "json-repair", + "numpy>=2.2.6", +] + +[project.entry-points."reme.plugins"] +beam = "reme_beam" + +[tool.setuptools.packages.find] +where = ["src"] +include = ["reme_beam*", "judge_beam*"] + +[tool.setuptools.package-data] +reme_beam = ["plugin.yaml", "*.yaml"] +judge_beam = ["*.yaml"] + +[build-system] +requires = ["setuptools>=77", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/plugins/beam/src/judge_beam/__init__.py b/plugins/beam/src/judge_beam/__init__.py new file mode 100644 index 00000000..7090e1ce --- /dev/null +++ b/plugins/beam/src/judge_beam/__init__.py @@ -0,0 +1,5 @@ +"""BEAM benchmark judge backend.""" + +from .llm_judge import BeamRubricJudgeStep + +__all__ = ["BeamRubricJudgeStep"] diff --git a/reme/steps/benchmark/beam/llm_judge.py b/plugins/beam/src/judge_beam/llm_judge.py similarity index 98% rename from reme/steps/benchmark/beam/llm_judge.py rename to plugins/beam/src/judge_beam/llm_judge.py index 879c82e3..fdadabbb 100644 --- a/reme/steps/benchmark/beam/llm_judge.py +++ b/plugins/beam/src/judge_beam/llm_judge.py @@ -24,10 +24,9 @@ from typing import List, Tuple import numpy as np from json_repair import repair_json -from ...base_step import BaseStep, Ref -from ....components import R -from ....components.as_embedding import BaseAsEmbedding -from ....enumeration import ComponentEnum +from reme.steps.base_step import BaseStep, Ref +from reme.components.as_embedding import BaseAsEmbedding +from reme.enumeration import ComponentEnum # --------------------------------------------------------------------------- @@ -231,7 +230,6 @@ def _event_ordering_score( } -@R.register("beam_rubric_judge_step") class BeamRubricJudgeStep(BaseStep): """Judge an LLM response against a list of rubric criteria. diff --git a/reme/steps/benchmark/beam/llm_judge.yaml b/plugins/beam/src/judge_beam/llm_judge.yaml similarity index 100% rename from reme/steps/benchmark/beam/llm_judge.yaml rename to plugins/beam/src/judge_beam/llm_judge.yaml diff --git a/reme/steps/benchmark/beam/__init__.py b/plugins/beam/src/reme_beam/__init__.py similarity index 56% rename from reme/steps/benchmark/beam/__init__.py rename to plugins/beam/src/reme_beam/__init__.py index e8c6e48f..2a5e8a7d 100644 --- a/reme/steps/benchmark/beam/__init__.py +++ b/plugins/beam/src/reme_beam/__init__.py @@ -1,11 +1,11 @@ -"""BEAM benchmark steps.""" +"""BEAM benchmark backends and application configuration for ReMe.""" from .agentic_answer import BeamAgenticAnswerStep -from .llm_judge import BeamRubricJudgeStep from .auto_memory import BeamAutoMemoryStep +from .search_v2 import SearchV2Step __all__ = [ "BeamAgenticAnswerStep", - "BeamRubricJudgeStep", "BeamAutoMemoryStep", + "SearchV2Step", ] diff --git a/reme/steps/benchmark/beam/agentic_answer.py b/plugins/beam/src/reme_beam/agentic_answer.py similarity index 80% rename from reme/steps/benchmark/beam/agentic_answer.py rename to plugins/beam/src/reme_beam/agentic_answer.py index 3b9a02fc..d6452ec0 100644 --- a/reme/steps/benchmark/beam/agentic_answer.py +++ b/plugins/beam/src/reme_beam/agentic_answer.py @@ -1,10 +1,8 @@ """BEAM agentic answer step – ReAct agent that answers questions using the search tool.""" -from ....components import R -from ..base import BaseAgenticAnswerStep +from reme.steps.benchmark import BaseAgenticAnswerStep -@R.register("beam_agentic_answer_step") class BeamAgenticAnswerStep(BaseAgenticAnswerStep): """Answer a BEAM probing question via ReAct agent with access to the search tool. diff --git a/reme/steps/benchmark/beam/agentic_answer.yaml b/plugins/beam/src/reme_beam/agentic_answer.yaml similarity index 100% rename from reme/steps/benchmark/beam/agentic_answer.yaml rename to plugins/beam/src/reme_beam/agentic_answer.yaml diff --git a/reme/steps/benchmark/beam/auto_memory.py b/plugins/beam/src/reme_beam/auto_memory.py similarity index 98% rename from reme/steps/benchmark/beam/auto_memory.py rename to plugins/beam/src/reme_beam/auto_memory.py index cccb0c28..378f97bf 100644 --- a/reme/steps/benchmark/beam/auto_memory.py +++ b/plugins/beam/src/reme_beam/auto_memory.py @@ -4,9 +4,8 @@ from datetime import datetime, timedelta from agentscope.message import Msg -from ...evolve.auto_memory import AutoMemoryStep, _normalize_msg_timestamp -from ...file_io import validate_session_id -from ....components import R +from reme.steps.evolve.auto_memory import AutoMemoryStep, _normalize_msg_timestamp +from reme.steps.file_io import validate_session_id # Runtime-context key carrying the 0-based line offset of the current segment # inside the full session file (segmented ingestion of long sessions). @@ -168,7 +167,6 @@ def _interpolate_timestamps(items: list[dict]) -> list[dict]: return result -@R.register("beam_auto_memory_step") class BeamAutoMemoryStep(AutoMemoryStep): """AutoMemoryStep variant that interpolates timestamps for BEAM sessions. diff --git a/reme/steps/benchmark/beam/auto_memory.yaml b/plugins/beam/src/reme_beam/auto_memory.yaml similarity index 100% rename from reme/steps/benchmark/beam/auto_memory.yaml rename to plugins/beam/src/reme_beam/auto_memory.yaml diff --git a/plugins/beam/src/reme_beam/plugin.yaml b/plugins/beam/src/reme_beam/plugin.yaml new file mode 100644 index 00000000..389653fc --- /dev/null +++ b/plugins/beam/src/reme_beam/plugin.yaml @@ -0,0 +1,120 @@ +backends: + beam_auto_memory_step: reme_beam.auto_memory:BeamAutoMemoryStep + beam_agentic_answer_step: reme_beam.agentic_answer:BeamAgenticAnswerStep + beam_rubric_judge_step: judge_beam.llm_judge:BeamRubricJudgeStep + beam_search_v2_step: reme_beam.search_v2:SearchV2Step + +application_defaults: + jobs: + search: + backend: base + description: "Hybrid workspace search (vector + BM25, RRF-fused) with deduplication." + parameters: + type: object + properties: + query: + type: string + description: "search query" + start_date: + type: string + description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded" + end_date: + type: string + description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded" + required: + - query + steps: + - backend: beam_search_v2_step + vector_weight: 0.7 + candidate_multiplier: 5.0 + expand_links: false + max_links_per_direction: 10 + + agentic_answer: + backend: base + description: "BEAM agentic answer job (ReAct agent with search tool)" + watch_dirs: [] + watch_suffixes: [] + parameters: + type: object + properties: + query: + type: string + description: "The query to ask" + query_time: + type: string + description: "ISO timestamp representing the query time" + default: "" + required: + - query + steps: + - backend: beam_agentic_answer_step + agent_wrapper: bench + + answer_judge: + backend: base + description: "BEAM rubric-based LLM-as-Judge: evaluate response against rubric criteria" + watch_dirs: [] + watch_suffixes: [] + parameters: + type: object + properties: + llm_response: + type: string + description: "The model's response to evaluate" + rubric: + type: array + description: "List of rubric criteria to check" + items: + type: string + probing_question: + type: string + description: "The original probing question" + default: "" + question_type: + type: string + description: "BEAM question type (e.g. event_ordering)" + default: "" + required: + - llm_response + - rubric + steps: + - backend: beam_rubric_judge_step + agent_wrapper: judge + + auto_memory: + backend: base + description: "Auto-memory: record conversation facts into a daily note" + parameters: + type: object + properties: + messages: + type: array + description: "messages" + items: + type: object + session_id: + type: string + description: "source conversation session identifier" + default: "" + memory_hint: + type: string + description: "optional hint" + date: + type: string + description: "YYYY-MM-DD daily note date; empty = infer from message timestamps or today" + default: "" + required: + - messages + steps: + - backend: beam_auto_memory_step + # Long sessions are split into turn-aligned segments and fed to the + # agent incrementally; <= 0 disables splitting. + max_segment_words: 10000 + + components: + as_llm: + default: + max_retries: 5 + judge: + retry_delay: 5.0 diff --git a/reme/steps/index/search_v2.py b/plugins/beam/src/reme_beam/search_v2.py similarity index 96% rename from reme/steps/index/search_v2.py rename to plugins/beam/src/reme_beam/search_v2.py index 558eefc4..77c02027 100644 --- a/reme/steps/index/search_v2.py +++ b/plugins/beam/src/reme_beam/search_v2.py @@ -12,14 +12,18 @@ import datetime import os from typing import Final -from ._dedup import _ToolContextDedupMixin -from ._source_format import ALL_RETURNED_MESSAGE, NO_RESULTS_MESSAGE, is_session_path, join_chunk_entries -from ._source_format import merge_session_chunk_intervals, render_chunk_entries -from ..base_step import BaseStep -from ..file_io import extract_daily_date -from ...components import R -from ...schema import FileChunk -from ...utils import expand_links +from reme.schema import FileChunk +from reme.steps.base_step import BaseStep +from reme.steps.file_io import extract_daily_date +from reme.steps.index._dedup import _ToolContextDedupMixin +from reme.steps.index._source_format import ( + ALL_RETURNED_MESSAGE, + NO_RESULTS_MESSAGE, + is_session_path, + join_chunk_entries, +) +from reme.steps.index._source_format import merge_session_chunk_intervals, render_chunk_entries +from reme.utils import expand_links _RRF_K: Final = 60 _MAX_CANDIDATES: Final = 200 @@ -37,7 +41,6 @@ def _default_limit() -> int: return _DEFAULT_LIMIT -@R.register("search_v2_step") class SearchV2Step(_ToolContextDedupMixin, BaseStep): """Hybrid search: run vector + keyword in parallel, fuse via RRF, filter, truncate.""" diff --git a/plugins/lme/LICENSE b/plugins/lme/LICENSE new file mode 100644 index 00000000..65c2c5cf --- /dev/null +++ b/plugins/lme/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 Alibaba Group + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/lme/README.md b/plugins/lme/README.md new file mode 100644 index 00000000..284ac7a4 --- /dev/null +++ b/plugins/lme/README.md @@ -0,0 +1,38 @@ +# LongMemEval plugin + +[中文说明](./README_ZH.md) + +This plugin owns the LongMemEval memory, agentic-answer and judge Steps, their prompts, +and their Job defaults in `plugin.yaml`. ReMe's built-in `benchmark.yaml` owns the +shared evaluation Jobs and components. Dataset handling, the runner and results remain +in [`benchmark/longmemeval`](../../benchmark/longmemeval/README.md). + +From the repository root, install ReMe and this plugin in editable mode before running the benchmark: + +```bash +python -m pip install -e ".[as]" +reme plugins install ./plugins/lme --editable +reme plugins validate lme +python benchmark/longmemeval/run.py +``` + +Editable installation registers the `lme` entry point while keeping source changes immediately +visible. The runner selects the built-in `benchmark` preset and explicitly enables `lme` for +each Application. Installing the plugin makes it discoverable but does not enable it globally. + +`plugin.yaml` registers backends and contributes the plugin-owned `auto_memory`, +`agentic_answer` and `answer_judge` Job defaults. Start the installed plugin with +`reme start config=benchmark plugins='["lme"]'`. The shared preset does not inherit +`default`: only declared Jobs run, indexing is manual, and neither scheduled dream +nor the optional `auto_dream` Job is enabled. +The existing `auto_memory`, `agentic_answer`, `answer_judge`, `bench` and `judge` +names and model environment variables are unchanged. Explicit application/CLI overrides +still take precedence. Installing this plugin does not start an evaluation. + +The shared answer base class lives in `reme.steps.benchmark.base_agentic_answer`. +The old core-owned `reme.steps.benchmark.lme` Python import path is removed. +Custom Python callers should import memory, search and answer Steps from `reme_lme`, and the +judge Step from `judge_lme`. After uninstalling, +Applications and CLI services must omit the plugin until it is installed again. +Uninstallation never removes datasets, workspaces or results. +Restart an existing service after changing plugins. diff --git a/plugins/lme/README_ZH.md b/plugins/lme/README_ZH.md new file mode 100644 index 00000000..176b2e80 --- /dev/null +++ b/plugins/lme/README_ZH.md @@ -0,0 +1,32 @@ +# LongMemEval 插件 + +[English](./README.md) + +插件包含 LongMemEval 的记忆、回答、评分 Step、提示词,以及 `plugin.yaml` 中对应的 Job 默认配置。 +ReMe 内置的 `benchmark.yaml` 负责公共评测 Job 和 Component;数据集处理、runner 和结果仍留在 +[`benchmark/longmemeval`](../../benchmark/longmemeval/README_ZH.md)。 + +在仓库根目录以 editable 模式安装 ReMe 和本插件,再运行评测: + +```bash +python -m pip install -e ".[as]" +reme plugins install ./plugins/lme --editable +reme plugins validate lme +python benchmark/longmemeval/run.py +``` + +editable 安装会注册 `lme` entry point,并让源码修改立即生效。runner 选择内置 `benchmark` +配置,并为每个 Application 显式启用 `lme`。安装只让插件可被发现,不会在所有应用中全局启用。 + +`plugin.yaml` 注册 backend,并通过 `application_defaults` 提供插件拥有的 `auto_memory`、 +`agentic_answer` 和 `answer_judge` Job。安装后使用 +`reme start config=benchmark plugins='["lme"]'`。公共评测配置不继承 `default`,只运行声明的 Job: +索引手动更新,dream 定时任务和可选的 `auto_dream` +均保持关闭。原有 `auto_memory`、`agentic_answer`、`answer_judge`、`bench`、`judge` 名称及模型环境变量 +保持不变,显式应用参数和 CLI 覆盖仍优先。安装或启用插件不会自动开始评测。 + +共享回答基类位于 `reme.steps.benchmark.base_agentic_answer`。 +原 `reme.steps.benchmark.lme` Python 导入路径已移除。自定义 Python 调用应从 `reme_lme` +导入记忆、搜索和回答 Step,并从 `judge_lme` 导入评判 Step。 +卸载插件后,Application 和 CLI 服务必须移除插件选择,直到再次安装。 +卸载不会删除数据集、工作区或结果。修改插件后需重启已有服务。 diff --git a/plugins/lme/pyproject.toml b/plugins/lme/pyproject.toml new file mode 100644 index 00000000..fa91dd74 --- /dev/null +++ b/plugins/lme/pyproject.toml @@ -0,0 +1,26 @@ +[project] +name = "reme-lme" +version = "0.1.0" +description = "LongMemEval benchmark plugin for ReMe." +readme = "README.md" +license = "Apache-2.0" +license-files = ["LICENSE"] +requires-python = ">=3.11" +dependencies = [ + "reme-ai[as]>=0.4.1.11", +] + +[project.entry-points."reme.plugins"] +lme = "reme_lme" + +[tool.setuptools.packages.find] +where = ["src"] +include = ["reme_lme*", "judge_lme*"] + +[tool.setuptools.package-data] +reme_lme = ["plugin.yaml", "*.yaml"] +judge_lme = ["*.yaml"] + +[build-system] +requires = ["setuptools>=77", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/plugins/lme/src/judge_lme/__init__.py b/plugins/lme/src/judge_lme/__init__.py new file mode 100644 index 00000000..127e5621 --- /dev/null +++ b/plugins/lme/src/judge_lme/__init__.py @@ -0,0 +1,5 @@ +"""LongMemEval benchmark judge backend.""" + +from .llm_judge import LmeAnswerJudgeStep + +__all__ = ["LmeAnswerJudgeStep"] diff --git a/reme/steps/benchmark/lme/llm_judge.py b/plugins/lme/src/judge_lme/llm_judge.py similarity index 96% rename from reme/steps/benchmark/lme/llm_judge.py rename to plugins/lme/src/judge_lme/llm_judge.py index 9c13c5a5..10dbb06e 100644 --- a/reme/steps/benchmark/lme/llm_judge.py +++ b/plugins/lme/src/judge_lme/llm_judge.py @@ -2,11 +2,9 @@ import re -from ...base_step import BaseStep -from ....components import R +from reme.steps.base_step import BaseStep -@R.register("lme_answer_judge_step") class LmeAnswerJudgeStep(BaseStep): """Evaluate whether an agent answer is correct against a golden answer.""" diff --git a/reme/steps/benchmark/lme/llm_judge.yaml b/plugins/lme/src/judge_lme/llm_judge.yaml similarity index 100% rename from reme/steps/benchmark/lme/llm_judge.yaml rename to plugins/lme/src/judge_lme/llm_judge.yaml diff --git a/reme/steps/benchmark/lme/__init__.py b/plugins/lme/src/reme_lme/__init__.py similarity index 54% rename from reme/steps/benchmark/lme/__init__.py rename to plugins/lme/src/reme_lme/__init__.py index 29005cc7..f1197900 100644 --- a/reme/steps/benchmark/lme/__init__.py +++ b/plugins/lme/src/reme_lme/__init__.py @@ -1,11 +1,11 @@ -"""LongMemEval benchmark steps.""" +"""LongMemEval benchmark backends and application configuration for ReMe.""" from .agentic_answer import LmeAgenticAnswerStep -from .llm_judge import LmeAnswerJudgeStep from .auto_memory import LmeAutoMemoryStep +from .search_v2 import SearchV2Step __all__ = [ "LmeAgenticAnswerStep", - "LmeAnswerJudgeStep", "LmeAutoMemoryStep", + "SearchV2Step", ] diff --git a/reme/steps/benchmark/lme/agentic_answer.py b/plugins/lme/src/reme_lme/agentic_answer.py similarity index 76% rename from reme/steps/benchmark/lme/agentic_answer.py rename to plugins/lme/src/reme_lme/agentic_answer.py index 70f8926f..e9017e5c 100644 --- a/reme/steps/benchmark/lme/agentic_answer.py +++ b/plugins/lme/src/reme_lme/agentic_answer.py @@ -1,10 +1,8 @@ """LongMemEval agentic answer step – ReAct agent that answers questions using the search tool.""" -from ....components import R -from ..base import BaseAgenticAnswerStep +from reme.steps.benchmark import BaseAgenticAnswerStep -@R.register("lme_agentic_answer_step") class LmeAgenticAnswerStep(BaseAgenticAnswerStep): """Answer a LongMemEval query via ReAct agent with access to the search tool. @@ -12,7 +10,7 @@ class LmeAgenticAnswerStep(BaseAgenticAnswerStep): ``search`` job tool to retrieve relevant memory chunks before generating a final answer. - Session-transcript compression in ``search_v2_step`` is controlled by the + Session-transcript compression in the plugin's search Step is controlled by the ``compress_session`` flag in the runtime context (set by the benchmark runner from ``evaluation.compress_session``); it is off by default. """ diff --git a/reme/steps/benchmark/lme/agentic_answer.yaml b/plugins/lme/src/reme_lme/agentic_answer.yaml similarity index 100% rename from reme/steps/benchmark/lme/agentic_answer.yaml rename to plugins/lme/src/reme_lme/agentic_answer.yaml diff --git a/reme/steps/benchmark/lme/auto_memory.py b/plugins/lme/src/reme_lme/auto_memory.py similarity index 97% rename from reme/steps/benchmark/lme/auto_memory.py rename to plugins/lme/src/reme_lme/auto_memory.py index bf3a678b..40c5b11c 100644 --- a/reme/steps/benchmark/lme/auto_memory.py +++ b/plugins/lme/src/reme_lme/auto_memory.py @@ -4,8 +4,7 @@ from datetime import datetime, timedelta from agentscope.message import Msg -from ...evolve.auto_memory import AutoMemoryStep, _normalize_msg_timestamp -from ....components import R +from reme.steps.evolve.auto_memory import AutoMemoryStep, _normalize_msg_timestamp def _parse_iso_seconds(value: str) -> datetime | None: @@ -115,7 +114,6 @@ def _interpolate_timestamps(items: list[dict]) -> list[dict]: return result -@R.register("lme_auto_memory_step") class LmeAutoMemoryStep(AutoMemoryStep): """AutoMemoryStep variant that interpolates timestamps for LongMemEval sessions. diff --git a/reme/steps/benchmark/lme/auto_memory.yaml b/plugins/lme/src/reme_lme/auto_memory.yaml similarity index 100% rename from reme/steps/benchmark/lme/auto_memory.yaml rename to plugins/lme/src/reme_lme/auto_memory.yaml diff --git a/plugins/lme/src/reme_lme/plugin.yaml b/plugins/lme/src/reme_lme/plugin.yaml new file mode 100644 index 00000000..e8f9513c --- /dev/null +++ b/plugins/lme/src/reme_lme/plugin.yaml @@ -0,0 +1,113 @@ +backends: + lme_auto_memory_step: reme_lme.auto_memory:LmeAutoMemoryStep + lme_agentic_answer_step: reme_lme.agentic_answer:LmeAgenticAnswerStep + lme_answer_judge_step: judge_lme.llm_judge:LmeAnswerJudgeStep + lme_search_v2_step: reme_lme.search_v2:SearchV2Step + +application_defaults: + jobs: + search: + backend: base + description: "Hybrid workspace search (vector + BM25, RRF-fused) with deduplication." + parameters: + type: object + properties: + query: + type: string + description: "search query" + start_date: + type: string + description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded" + end_date: + type: string + description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded" + required: + - query + steps: + - backend: lme_search_v2_step + vector_weight: 0.7 + candidate_multiplier: 5.0 + expand_links: false + max_links_per_direction: 10 + + agentic_answer: + backend: base + description: "LongMemEval agentic answer job (ReAct agent with search tool)" + watch_dirs: [] + watch_suffixes: [] + parameters: + type: object + properties: + query: + type: string + description: "The query to ask" + query_time: + type: string + description: "ISO timestamp representing the query time" + default: "" + required: + - query + steps: + - backend: lme_agentic_answer_step + agent_wrapper: bench + + answer_judge: + backend: base + description: "LLM-as-Judge: evaluate agent answer against golden answer" + watch_dirs: [] + watch_suffixes: [] + parameters: + type: object + properties: + query: + type: string + description: "The question being asked" + agent_answer: + type: string + description: "The model's answer to evaluate" + golden_answer: + type: string + description: "The correct/golden answer" + question_type: + type: string + description: "The question type for prompt selection" + default: "" + required: + - query + - agent_answer + - golden_answer + steps: + - backend: lme_answer_judge_step + agent_wrapper: judge + + auto_memory: + backend: base + description: "Auto-memory: record conversation facts into a daily note" + parameters: + type: object + properties: + messages: + type: array + description: "messages" + items: + type: object + session_id: + type: string + description: "source conversation session identifier" + default: "" + memory_hint: + type: string + description: "optional hint" + date: + type: string + description: "YYYY-MM-DD daily note date; empty = infer from message timestamps or today" + default: "" + required: + - messages + steps: + - backend: lme_auto_memory_step + + components: + as_llm: + default: + max_retries: 3 diff --git a/plugins/lme/src/reme_lme/search_v2.py b/plugins/lme/src/reme_lme/search_v2.py new file mode 100644 index 00000000..77c02027 --- /dev/null +++ b/plugins/lme/src/reme_lme/search_v2.py @@ -0,0 +1,333 @@ +"""Hybrid search (v2) over file_store using RRF fusion of vector + keyword results. + +This is the local fork of the upstream search step. It uses +:class:`_ToolContextDedupMixin` for subset-aware interval-merging dedup and +:func:`render_chunk_entries` / :func:`join_chunk_entries` for session-aware +chunk formatting with +:data:`ALL_RETURNED_MESSAGE` / :data:`NO_RESULTS_MESSAGE` notices. +""" + +import asyncio +import datetime +import os +from typing import Final + +from reme.schema import FileChunk +from reme.steps.base_step import BaseStep +from reme.steps.file_io import extract_daily_date +from reme.steps.index._dedup import _ToolContextDedupMixin +from reme.steps.index._source_format import ( + ALL_RETURNED_MESSAGE, + NO_RESULTS_MESSAGE, + is_session_path, + join_chunk_entries, +) +from reme.steps.index._source_format import merge_session_chunk_intervals, render_chunk_entries +from reme.utils import expand_links + +_RRF_K: Final = 60 +_MAX_CANDIDATES: Final = 200 +_DEFAULT_LIMIT_ENV: Final = "REME_SEARCH_LIMIT" +_DEFAULT_LIMIT: Final = 5 + + +def _default_limit() -> int: + value = os.getenv(_DEFAULT_LIMIT_ENV) + if value is None: + return _DEFAULT_LIMIT + try: + return int(value) + except ValueError: + return _DEFAULT_LIMIT + + +class SearchV2Step(_ToolContextDedupMixin, BaseStep): + """Hybrid search: run vector + keyword in parallel, fuse via RRF, filter, truncate.""" + + def __init__( + self, + *args, + seen_ttl_hours: float = 24, + **kwargs, + ): + super().__init__(*args, **kwargs) + self.seen_ttl_hours = seen_ttl_hours + + @staticmethod + def _rrf_merge( + vector: list[FileChunk], + keyword: list[FileChunk], + vector_weight: float, + ) -> list[FileChunk]: + """Fuse two ranked lists with Reciprocal Rank Fusion, keyed by chunk.id.""" + text_weight = 1.0 - vector_weight + merged: dict[str, FileChunk] = {} + + for rank, chunk in enumerate(vector, start=1): + contrib = vector_weight / (_RRF_K + rank) + c = chunk.model_copy(deep=False) + c.scores = {**chunk.scores, "vector": chunk.scores.get("vector", chunk.score), "score": contrib} + merged[c.id] = c + + for rank, chunk in enumerate(keyword, start=1): + contrib = text_weight / (_RRF_K + rank) + existing = merged.get(chunk.id) + if existing is not None: + existing.scores = { + **existing.scores, + "keyword": chunk.scores.get("keyword", chunk.score), + "score": existing.scores["score"] + contrib, + } + else: + c = chunk.model_copy(deep=False) + c.scores = {**chunk.scores, "keyword": chunk.scores.get("keyword", chunk.score), "score": contrib} + merged[c.id] = c + + results = list(merged.values()) + results.sort(key=lambda r: r.score, reverse=True) + return results + + @staticmethod + def _format_scores(scores: dict[str, float], hybrid: bool) -> str: + """Format scores for the answer line: always show fused; show per-branch when hybrid.""" + parts = [f"score={scores.get('score', 0.0):.4f}"] + if hybrid: + for k in ("vector", "keyword"): + v = scores.get(k) + parts.append(f"{k}={v:.4f}" if v is not None else f"{k}=-") + return " ".join(parts) + + async def execute(self): + assert self.context is not None + query: str = (self.context.get("query", "") or "").strip() + limit: int = int(self.context.get("limit") or _default_limit()) + min_score: float = float(self.context.get("min_score") or 0.0) + # vector_weight: prefer agent-supplied context value; fallback to YAML kwargs / default 0.7. + # Convertible numeric inputs are clipped to [0.0, 1.0]; non-numeric inputs are silently ignored. + raw_vw = self.context.get("vector_weight") + vector_weight: float | None = None + if raw_vw is not None: + try: + vector_weight = float(raw_vw) + except (TypeError, ValueError): + self.logger.warning( + f"[{self.name}] non-numeric vector_weight={raw_vw!r}; ignoring and using default 0.7", + ) + vector_weight = None + if vector_weight is None: + vector_weight = float(self.kwargs.get("vector_weight", 0.7)) + vector_weight = max(0.0, min(1.0, vector_weight)) + candidate_multiplier: float = float(self.kwargs.get("candidate_multiplier", 5.0)) + expand_links_enabled: bool = bool(self.kwargs.get("expand_links", True)) + max_links_per_direction: int = int(self.kwargs.get("max_links_per_direction", 10)) + tool_context_id: str = (self.context.get("tool_context_id", "") or "").strip() + # Injected value takes precedence over YAML kwargs; check existence + # (not truthiness) so an explicit False can disable a YAML-true flag. + _strict_date_filter = self.context.get("strict_date_filter") + if _strict_date_filter is None: + _strict_date_filter = self.kwargs.get("strict_date_filter", False) + strict_date_filter: bool = bool(_strict_date_filter) + + if not query: + self.context.response.success = False + self.context.response.answer = "Error: query cannot be empty" + return self.context.response + assert limit > 0, f"limit must be positive, got {limit}" + + candidates = min(_MAX_CANDIDATES, max(1, int(limit * candidate_multiplier))) + search_filter: dict = dict(self.context.get("search_filter", {}) or {}) + + # Promote top-level date parameters into search_filter for file_store. + for date_key in ("start_date", "end_date"): + value = self.context.get(date_key) + if value and date_key not in search_filter: + search_filter[date_key] = value + + # Validate and normalize date filters before they reach file_store. + # _matches_search_filter does lexicographic string comparison against + # path_date (always a canonical YYYY-MM-DD), so raw caller values like + # "2026-2-28" or "abc" would produce silently wrong results. + for date_key in ("start_date", "end_date"): + raw = search_filter.get(date_key) + if raw is None: + continue + normalized = extract_daily_date(raw) + if normalized is None: + # Fallback: accept non-zero-padded dates like "2024-1-5". + try: + normalized = ( + datetime.datetime.strptime( + str(raw).strip(), + "%Y-%m-%d", + ) + .date() + .isoformat() + ) + except ValueError: + self.logger.warning( + f"Ignoring invalid {date_key}={raw!r}; " f"expected a valid YYYY-MM-DD date.", + ) + del search_filter[date_key] + continue + search_filter[date_key] = normalized + + if strict_date_filter: + search_filter["strict_date_filter"] = True + + vector_results, keyword_results = await asyncio.gather( + self.file_store.vector_search(query, candidates, search_filter), + self.file_store.keyword_search(query, candidates, search_filter), + ) + + self.logger.info( + f"[{self.name}] query={query!r} candidates={candidates} " + f"vector_hits={len(vector_results)} keyword_hits={len(keyword_results)}", + ) + + hybrid = bool(vector_results) and bool(keyword_results) + if not vector_results and not keyword_results: + fused: list[FileChunk] = [] + elif not keyword_results: + fused = vector_results + elif not vector_results: + fused = keyword_results + else: + fused = self._rrf_merge(vector_results, keyword_results, vector_weight) + + if min_score > 0.0: + fused = [c for c in fused if c.score >= min_score] + + pre_dedup_count = 0 + dedup: dict | None = None + if tool_context_id: + pre_dedup_count = len(fused) + fused, dedup = self._dedupe_tool_context( + fused, + tool_context_id, + limit, + clock=self.kwargs.get("clock"), + ttl_override=self.kwargs.get("tool_context_chunk_ttl_seconds"), + ) + else: + fused = fused[:limit] + + unique_paths = list(dict.fromkeys(c.path for c in fused)) + link_expansion: dict[str, dict] = ( + await expand_links(self.file_store, unique_paths, max_links_per_direction) if expand_links_enabled else {} + ) + + session_dir = self.config_value("session_dir") + entries = render_chunk_entries( + merge_session_chunk_intervals(fused, session_dir), + session_dir, + score_fn=lambda c: self._format_scores(c.scores, hybrid), + link_expansion=link_expansion, + ) + if self._session_compress_enabled(): + await self._compress_session_entries(entries, query, session_dir) + self.context.response.answer = join_chunk_entries(entries) + if not fused: + self.context.response.answer = ALL_RETURNED_MESSAGE if pre_dedup_count > 0 else NO_RESULTS_MESSAGE + self.context.response.metadata["results"] = [ + c.model_dump(exclude_none=True, exclude={"embedding"}) for c in fused + ] + self.context.response.metadata["link_expansion"] = link_expansion + self.context.response.metadata["counts"] = { + "vector": len(vector_results), + "keyword": len(keyword_results), + "returned": len(fused), + "hybrid": hybrid, + } + if dedup is not None: + self.context.response.metadata["dedup"] = dedup + return self.context.response + + def _session_compress_enabled(self) -> bool: + """True when the injected ``_search._compress.session`` flag is truthy.""" + assert self.context is not None + search_cfg: dict = self.context.get("_search") or {} + value = (search_cfg.get("_compress") or {}).get("session") + return value is True or str(value).strip().lower() == "true" + + async def _compress_session_entries(self, entries: list[dict[str, str]], query: str, session_dir: str) -> None: + """Compress session-transcript entry bodies in place via the ``compressor`` job. + + Only entries whose ``path`` points at a raw session transcript are + compressed; other entries and all non-``body`` fields stay untouched. + When ``_search.type`` is ``query-independent`` the compressor runs + without queries (generic compression); otherwise (``query-aware``, + the default) it receives the injected ``_search.queries`` plus the + current search query. + + The compressor receives the already-rendered body (one message per + line) stripped. Its output is adopted whenever the compressor + succeeded and the result is not longer than the input; adopted bodies + get a leading ``compressed session chunk:`` marker so downstream + consumers can tell them from verbatim transcripts. + + Degrades gracefully when the ``compressor`` job is missing from the + active config: the whole method becomes a no-op and a warning is + logged, so search behaves as if compression were disabled. This + avoids a hard ``Job compressor not found`` failure when a benchmark + config forgets to define the compressor job/component. + + Per-entry exceptions raised by the compressor job (e.g. a temporary + LLM outage) are caught inside ``compress`` so they never propagate + through ``asyncio.gather``: the failing entry keeps its original body + while the remaining entries are still compressed, preserving already + retrieved search results. + """ + assert self.context is not None + # Guard: when the compressor job is missing from the active config + # (e.g. a benchmark config that forgot to define it), degrade + # gracefully to the no-compression behavior instead of raising + # "Job compressor not found" from run_job below. + # Skipped when there is no app_context (e.g. unit tests that mock + # run_job directly), so the mock can still drive compression. + if self.app_context is not None and self.get_job("compressor") is None: + self.logger.warning( + f"[{self.name}] compressor job not found in config; " + "skipping session chunk compression (degrading to uncompressed behavior)", + ) + return + search_cfg: dict = self.context.get("_search") or {} + query_type = str(search_cfg.get("type") or "query-aware").strip().lower() + if query_type == "query-independent": + queries: list[str] = [] + else: + queries = [str(q).strip() for q in (search_cfg.get("queries") or []) if str(q).strip()] + if query and query not in queries: + queries.append(query) + + async def compress(entry: dict[str, str]) -> None: + path = entry.get("path", "") + body = (entry.get("body", "") or "").strip() + if not body: + return + try: + response = await self.run_job("compressor", text=body, queries=queries) + except Exception as exc: # pylint: disable=broad-except + self.logger.warning( + f"[{self.name}] session body compression raised path={path!r} " f"error={exc!r}; keeping original", + ) + return + compressed = str(response.answer or "").strip() + if not response.success or not compressed: + self.logger.warning( + f"[{self.name}] session body compression failed path={path!r} " + f"success={response.success} answer={compressed[:100]!r}; keeping original", + ) + return + if len(compressed) > len(body): + self.logger.info( + f"[{self.name}] compressed body longer than original " + f"({len(compressed)} > {len(body)}) path={path!r}; keeping original", + ) + return + entry["body"] = f"compressed session chunk:\n{compressed}" + + targets = [e for e in entries if is_session_path(e.get("path", ""), session_dir)] + if not targets: + return + self.logger.info(f"[{self.name}] compressing {len(targets)} session entries with {len(queries)} queries") + await asyncio.gather(*(compress(entry) for entry in targets)) diff --git a/reme/config/beam.yaml b/reme/config/beam.yaml deleted file mode 100644 index c3d6bda1..00000000 --- a/reme/config/beam.yaml +++ /dev/null @@ -1,635 +0,0 @@ -# BEAM benchmark config — based on longmemeval.yaml (split) -# All background/cron jobs are converted to base (manually callable). -# Use with: resolve_app_config(config="beam.yaml", ...) - -service: - backend: http - -jobs: - # ── BEAM agentic answer (ReAct agent + search tool) ── - agentic_answer: - backend: base - description: "BEAM agentic answer job (ReAct agent with search tool)" - watch_dirs: [] - watch_suffixes: [] - parameters: - type: object - properties: - query: - type: string - description: "The query to ask" - query_time: - type: string - description: "ISO timestamp representing the query time" - default: "" - required: - - query - steps: - - backend: beam_agentic_answer_step - agent_wrapper: bench - - # ── BEAM rubric-based LLM-as-Judge ── - answer_judge: - backend: base - description: "BEAM rubric-based LLM-as-Judge: evaluate response against rubric criteria" - watch_dirs: [] - watch_suffixes: [] - parameters: - type: object - properties: - llm_response: - type: string - description: "The model's response to evaluate" - rubric: - type: array - description: "List of rubric criteria to check" - items: - type: string - probing_question: - type: string - description: "The original probing question" - default: "" - question_type: - type: string - description: "BEAM question type (e.g. event_ordering)" - default: "" - required: - - llm_response - - rubric - steps: - - backend: beam_rubric_judge_step - agent_wrapper: judge - - # ── Manual index update (replaces index_update_loop background) ── - index_update: - backend: base - description: "Manually trigger incremental index update for watched dirs." - watch_dirs: [daily_dir, digest_dir, session_dir/dialog] - watch_suffixes: [md, jsonl] - parameters: - type: object - properties: {} - steps: - - backend: init_changes_step - monitor_type: file_store - monitor_name: default - dispatch_steps: [update_index_step] - - # ── Manual digest catalog update (replaces digest_watch_loop background) ── - digest_update: - backend: base - description: "Manually trigger digest catalog update." - watch_dirs: [daily_dir, digest_dir] - watch_suffixes: [md] - parameters: - type: object - properties: {} - steps: - - backend: init_changes_step - monitor_type: file_catalog - monitor_name: digest - dispatch_steps: - - backend: update_catalog_step - file_catalog: digest - - backend: log_changes_step - - # ── Auto dream (same as default.yaml auto_dream, base mode) ── - # auto_dream: - # backend: base - # description: "Auto-dream: scan today's day-index and daily notes, globally extract merged units/topics, integrate digest units, write interests.yaml, and persist the dream catalog." - # parameters: - # type: object - # properties: - # date: - # type: string - # description: "YYYY-MM-DD to scan; defaults to today in the dreamer's timezone" - # default: "" - # hint: - # type: string - # description: "caller guidance passed through to dream extract/integrate" - # default: "" - # scan_days: - # type: integer - # description: "number of recent daily directories to scan, ending at date" - # default: 2 - # max_units: - # type: integer - # description: "maximum number of extracted memory units" - # default: 5 - # topic_count: - # type: integer - # description: "maximum number of final daily interest topics" - # default: 3 - # topic_diversity_days: - # type: integer - # description: "number of previous interests.yaml days to avoid repeating" - # default: 7 - # steps: - # - backend: dream_extract_step - # file_catalog: dream - # topic_session_id: interests - # scan_days: 2 - # max_units: 5 - # - backend: dream_integrate_step - # - backend: dream_topics_step - # topic_count: 3 - # topic_diversity_days: 7 - # - backend: dream_finish_step - # file_catalog: dream - - # ── Auto memory (same as default.yaml) ── - auto_memory: - backend: base - description: "Auto-memory: record conversation facts into a daily note" - parameters: - type: object - properties: - messages: - type: array - description: "messages" - items: - type: object - session_id: - type: string - description: "source conversation session identifier" - default: "" - memory_hint: - type: string - description: "optional hint" - date: - type: string - description: "YYYY-MM-DD daily note date; empty = infer from message timestamps or today" - default: "" - required: - - messages - steps: - - backend: beam_auto_memory_step - # Long sessions are split into turn-aligned segments and fed to the - # agent incrementally; each segment holds at most this many words. - # ("segment" here is a slice of the message list, unrelated to file - # chunking in the index.) <= 0 disables splitting. - max_segment_words: 10000 - - # ── Text compression (direct LLM call, no agent) ── - compressor: - backend: base - description: "Compress text via a direct LLM call, optionally guided by queries as relevance filter" - parameters: - type: object - properties: - text: - type: string - description: "the text to compress" - queries: - type: array - description: "optional list of queries; content potentially relevant to any query is kept, content certainly irrelevant to all queries may be dropped" - items: - type: string - default: [] - required: - - text - steps: - - backend: compressor_step - as_llm: compressor - - # ── Reindex (derived search indexes only) ── - reindex: - backend: base - description: "rebuild BM25 and/or embedding indexes from current file_chunks" - parameters: - type: object - properties: - scope: - type: string - enum: [all, bm25, embedding] - default: all - steps: - - backend: reindex_step - - # ── Search ── - # start_date: - # type: string - # description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded" - # end_date: - # type: string - # description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded" - - search: - backend: base - description: "Hybrid workspace search (vector + BM25, RRF-fused) with deduplication." - parameters: - type: object - properties: - query: - type: string - description: "search query" - start_date: - type: string - description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded" - end_date: - type: string - description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded" - # vector_weight: - # type: number - # description: >- - # Optional weight balancing vector similarity vs BM25 keyword matching in the - # RRF fusion. Recommended value is 0.7, which provides a good balance between - # semantic (vector) similarity and lexical (BM25) matching. Values close to 0 - # emphasize BM25 keyword matching, values close to 1 emphasize vector semantic - # similarity. - required: - - query - steps: - - backend: search_v2_step - vector_weight: 0.7 - candidate_multiplier: 5.0 - expand_links: false - max_links_per_direction: 10 - - add_draft: - backend: base - description: "Append text to the current draft list." - parameters: - type: object - properties: - text: - type: string - description: "draft text to append" - required: - - text - steps: - - backend: add_draft_step - - read_all_draft: - backend: base - description: "Read all draft text previously appended in the current tool context." - parameters: - type: object - properties: { } - steps: - - backend: read_all_draft_step - - python_execute: - backend: base - description: "Execute Python code and return printed stdout." - parameters: - type: object - properties: - code: - type: string - description: "Python code to execute. Print the final result to stdout." - timeout: - type: number - description: "Execution timeout in seconds; defaults to 60." - required: - - code - steps: - - backend: python_execute_step - - # ── File I/O jobs (needed by auto_memory agent tools) ── - daily_list: - backend: base - description: "List notes under a single day." - parameters: - type: object - properties: - date: - type: string - description: "YYYY-MM-DD; empty = today" - default: "" - steps: - - backend: daily_list_step - - daily_reindex: - backend: base - description: "Rebuild the day-index page daily/.md." - parameters: - type: object - properties: - date: - type: string - description: "YYYY-MM-DD; empty = today" - default: "" - steps: - - backend: daily_reindex_step - - frontmatter_update: - backend: base - description: "Merge key-values into a file's frontmatter." - parameters: - type: object - properties: - path: - type: string - description: "workspace-relative path" - metadata: - type: object - description: "key-values to merge" - required: - - path - - metadata - steps: - - backend: frontmatter_update_step - - move: - backend: base - description: "Move / rename a workspace file." - parameters: - type: object - properties: - src_path: - type: string - description: "workspace-relative source" - dst_path: - type: string - description: "workspace-relative destination" - overwrite: - type: boolean - default: false - retarget: - type: boolean - default: true - required: - - src_path - - dst_path - steps: - - backend: move_step - - read: - backend: base - description: "Read a markdown file under the workspace." - parameters: - type: object - properties: - path: - type: string - description: "workspace-relative path; markdown only" - start_line: - type: integer - end_line: - type: integer - required: - - path - steps: - - backend: read_step - with_neighbors: false - max_neighbors_per_direction: 10 - - write: - backend: base - description: "Write a markdown file." - parameters: - type: object - properties: - path: - type: string - name: - type: string - description: - type: string - content: - type: string - metadata: - type: object - required: - - path - - name - - description - - content - steps: - - backend: write_step - - daily_write: - backend: base - description: "Write a daily markdown note." - parameters: - type: object - properties: - name: - type: string - description: - type: string - session_id: - type: string - content: - type: string - date: - type: string - default: "" - metadata: - type: object - required: - - name - - description - - session_id - - content - steps: - - backend: daily_write_step - - edit: - backend: base - description: "Find-and-replace in a markdown file." - parameters: - type: object - properties: - path: - type: string - old: - type: string - new: - type: string - default: "" - required: - - path - - old - - new - steps: - - backend: edit_step - - frontmatter_read: - backend: base - description: "Read a file's frontmatter as a dict." - parameters: - type: object - properties: - path: - type: string - required: - - path - steps: - - backend: frontmatter_read_step - - node_search: - backend: base - description: "Digest node recall." - parameters: - type: object - properties: - query: - type: string - limit: - type: integer - default: 20 - required: - - query - steps: - - backend: node_search_step - vector_weight: 0.7 - candidate_multiplier: 5.0 - -components: - tokenizer: - default: - backend: regex - - as_embedding: - default: - backend: ${EMBEDDING_BACKEND:-openai} - model: ${EMBEDDING_MODEL_NAME:-text-embedding-v4} - credential: - api_key: ${EMBEDDING_API_KEY:-} - base_url: ${EMBEDDING_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1} - dimensions: 1024 - - embedding_store: - default: - backend: local - as_embedding: default - - as_llm: - default: - backend: ${LLM_BACKEND:-openai} - model: ${LLM_MODEL_NAME:-qwen3.6-flash} - stream: true - context_size: 200000 - max_retries: 5 - retry_delay: 5.0 - credential: - api_key: ${LLM_API_KEY:-} - base_url: ${LLM_BASE_URL:-} - parameters: - max_tokens: 65536 - thinking_enable: false - judge: - backend: ${LLM_BACKEND:-openai} - model: ${JUDGE_MODEL_NAME:-qwen3.7-max} - stream: false - context_size: 200000 - max_retries: 5 - retry_delay: 5.0 - credential: - api_key: ${LLM_API_KEY:-} - base_url: ${LLM_BASE_URL:-} - parameters: - max_tokens: 65536 - thinking_enable: false - bench: - backend: ${LLM_BACKEND:-openai} - model: ${BENCH_MODEL_NAME:-qwen3.7-max} - stream: true - context_size: 400000 - max_retries: 5 - retry_delay: 5.0 - credential: - api_key: ${LLM_API_KEY:-} - base_url: ${LLM_BASE_URL:-} - parameters: - max_tokens: 65536 - thinking_enable: true - compressor: - backend: ${LLM_BACKEND:-openai} - model: ${LLM_MODEL_NAME:-qwen3.6-flash} - stream: false - context_size: 200000 - max_retries: 5 - retry_delay: 5.0 - credential: - api_key: ${LLM_API_KEY:-} - base_url: ${LLM_BASE_URL:-} - parameters: - max_tokens: 65536 - thinking_enable: false - - agent_wrapper: - default: - backend: agentscope - as_llm: default - permission_mode: bypass - react_config: - max_iters: 30 - context_config: - trigger_ratio: 0.8 - reserve_ratio: 0.1 - tool_result_limit: 50000 - model_config: - max_retries: 1 - judge: - backend: agentscope - as_llm: judge - permission_mode: bypass - react_config: - max_iters: 1 - context_config: - trigger_ratio: 0.8 - reserve_ratio: 0.1 - tool_result_limit: 50000 - model_config: - max_retries: 1 - bench: - backend: agentscope - as_llm: bench - permission_mode: bypass - react_config: - max_iters: 30 - context_config: - trigger_ratio: 0.8 - reserve_ratio: 0.1 - tool_result_limit: 50000 - model_config: - max_retries: 1 - - file_graph: - default: - backend: local - - file_catalog: - default: - backend: local - resource: - backend: local - digest: - backend: local - dream: - backend: local - - file_chunker: - markdown: - backend: markdown - supported_extensions: [ "md" ] - embed_toc: true - max_ast_sections: 100 - include_frontmatter_in_metadata: false - include_frontmatter_keys_in_metadata: [] # empty = all non-empty frontmatter keys - json: - backend: json - supported_extensions: [ "json" ] - jsonl: - backend: jsonl - supported_extensions: [ "jsonl" ] # noqa: keep #314 chunker scope intact after #325 - max_chars: 4000 - default: - backend: default - supported_extensions: ["txt","log"] - - keyword_index: - default: - backend: bm25 - tokenizer: default - - file_store: - default: - backend: local - store_name: local - embedding_store: default - keyword_index: default - file_graph: default diff --git a/reme/config/lme.yaml b/reme/config/benchmark.yaml similarity index 74% rename from reme/config/lme.yaml rename to reme/config/benchmark.yaml index 24ec4e40..643c3a62 100644 --- a/reme/config/lme.yaml +++ b/reme/config/benchmark.yaml @@ -1,63 +1,11 @@ -# LongMemEval benchmark config — based on longmemeval.yaml (split) -# All background/cron jobs are converted to base (manually callable). -# Use with: resolve_app_config(config="lme.yaml", ...) +# Shared benchmark application preset; independent of the default service config. +# Background and cron jobs are omitted in favor of manually callable base jobs. +# Benchmark plugins contribute their own jobs through plugin.yaml. service: backend: http jobs: - # ── LongMemEval agentic answer (ReAct agent + search tool) ── - agentic_answer: - backend: base - description: "LongMemEval agentic answer job (ReAct agent with search tool)" - watch_dirs: [] - watch_suffixes: [] - parameters: - type: object - properties: - query: - type: string - description: "The query to ask" - query_time: - type: string - description: "ISO timestamp representing the query time" - default: "" - required: - - query - steps: - - backend: lme_agentic_answer_step - agent_wrapper: bench - - # ── LLM-as-Judge for evaluating answers ── - answer_judge: - backend: base - description: "LLM-as-Judge: evaluate agent answer against golden answer" - watch_dirs: [] - watch_suffixes: [] - parameters: - type: object - properties: - query: - type: string - description: "The question being asked" - agent_answer: - type: string - description: "The model's answer to evaluate" - golden_answer: - type: string - description: "The correct/golden answer" - question_type: - type: string - description: "The question type for prompt selection" - default: "" - required: - - query - - agent_answer - - golden_answer - steps: - - backend: lme_answer_judge_step - agent_wrapper: judge - # ── Manual index update (replaces index_update_loop background) ── index_update: backend: base @@ -135,34 +83,6 @@ jobs: # - backend: dream_finish_step # file_catalog: dream - # ── Auto memory (same as default.yaml) ── - auto_memory: - backend: base - description: "Auto-memory: record conversation facts into a daily note" - parameters: - type: object - properties: - messages: - type: array - description: "messages" - items: - type: object - session_id: - type: string - description: "source conversation session identifier" - default: "" - memory_hint: - type: string - description: "optional hint" - date: - type: string - description: "YYYY-MM-DD daily note date; empty = infer from message timestamps or today" - default: "" - required: - - messages - steps: - - backend: lme_auto_memory_step - # ── Text compression (direct LLM call, no agent) ── compressor: backend: base @@ -199,46 +119,6 @@ jobs: steps: - backend: reindex_step - # ── Search ── - # start_date: - # type: string - # description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded" - # end_date: - # type: string - # description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded" - - search: - backend: base - description: "Hybrid workspace search (vector + BM25, RRF-fused) with deduplication." - parameters: - type: object - properties: - query: - type: string - description: "search query" - start_date: - type: string - description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded" - end_date: - type: string - description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded" - # vector_weight: - # type: number - # description: >- - # Optional weight balancing vector similarity vs BM25 keyword matching in the - # RRF fusion. Recommended value is 0.7, which provides a good balance between - # semantic (vector) similarity and lexical (BM25) matching. Values close to 0 - # emphasize BM25 keyword matching, values close to 1 emphasize vector semantic - # similarity. - required: - - query - steps: - - backend: search_v2_step - vector_weight: 0.7 - candidate_multiplier: 5.0 - expand_links: false - max_links_per_direction: 10 - add_draft: backend: base description: "Append text to the current draft list." @@ -326,7 +206,7 @@ jobs: move: backend: base - description: "Move / rename a workspace file." + description: "Move / rename a workspace file; rewrites inbound wikilinks by default." parameters: type: object properties: @@ -338,9 +218,11 @@ jobs: description: "workspace-relative destination" overwrite: type: boolean + description: "overwrite if dst exists" default: false retarget: type: boolean + description: "rewrite [[src]] → [[dst]] across the workspace" default: true required: - src_path @@ -359,8 +241,10 @@ jobs: description: "workspace-relative path; markdown only" start_line: type: integer + description: "first line (1-based, inclusive)" end_line: type: integer + description: "last line (1-based, inclusive)" required: - path steps: @@ -370,20 +254,25 @@ jobs: write: backend: base - description: "Write a markdown file." + description: "Write a markdown file (create or overwrite) with name/description frontmatter." parameters: type: object properties: path: type: string + description: "workspace-relative path; markdown only" name: type: string + description: "frontmatter name" description: type: string + description: "frontmatter description" content: type: string + description: "body" metadata: type: object + description: "Optional extra frontmatter fields (md only)." required: - path - name @@ -394,23 +283,29 @@ jobs: daily_write: backend: base - description: "Write a daily markdown note." + description: "Write a daily markdown note with conversation source frontmatter." parameters: type: object properties: name: type: string + description: "daily note filename stem and frontmatter name" description: type: string + description: "frontmatter description" session_id: type: string + description: "source conversation session identifier" content: type: string + description: "body" date: type: string + description: "YYYY-MM-DD daily note date; empty = today" default: "" metadata: type: object + description: "Optional extra frontmatter fields." required: - name - description @@ -421,16 +316,19 @@ jobs: edit: backend: base - description: "Find-and-replace in a markdown file." + description: "Find-and-replace in a markdown file (all occurrences)." parameters: type: object properties: path: type: string + description: "workspace-relative path" old: type: string + description: "text to find" new: type: string + description: "replacement" default: "" required: - path @@ -447,6 +345,7 @@ jobs: properties: path: type: string + description: "workspace-relative path" required: - path steps: @@ -454,14 +353,16 @@ jobs: node_search: backend: base - description: "Digest node recall." + description: "Digest node recall — given a candidate abstraction's name+description, surface existing digest nodes similar enough to either dedup against or link to as related." parameters: type: object properties: query: type: string + description: "search query" limit: type: integer + description: "max digest nodes to return" default: 20 required: - query @@ -495,7 +396,6 @@ components: model: ${LLM_MODEL_NAME:-qwen3.6-flash} stream: true context_size: 200000 - max_retries: 3 retry_delay: 5.0 credential: api_key: ${LLM_API_KEY:-} diff --git a/reme/steps/benchmark/__init__.py b/reme/steps/benchmark/__init__.py index a5fdc8aa..c0d50414 100644 --- a/reme/steps/benchmark/__init__.py +++ b/reme/steps/benchmark/__init__.py @@ -1,17 +1,5 @@ -"""Benchmark steps.""" +"""Shared benchmark steps; concrete implementations live in plugins.""" -from . import base, lme, beam -from .base import BaseAgenticAnswerStep -from .lme import LmeAgenticAnswerStep, LmeAnswerJudgeStep -from .beam import BeamAgenticAnswerStep, BeamRubricJudgeStep +from .base_agentic_answer import BaseAgenticAnswerStep -__all__ = [ - "BaseAgenticAnswerStep", - "LmeAgenticAnswerStep", - "LmeAnswerJudgeStep", - "BeamAgenticAnswerStep", - "BeamRubricJudgeStep", - "base", - "lme", - "beam", -] +__all__ = ["BaseAgenticAnswerStep"] diff --git a/reme/steps/benchmark/base/__init__.py b/reme/steps/benchmark/base/__init__.py deleted file mode 100644 index 2b31bb0d..00000000 --- a/reme/steps/benchmark/base/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Shared base classes for benchmark steps.""" - -from .agentic_answer import BaseAgenticAnswerStep - -__all__ = [ - "BaseAgenticAnswerStep", -] diff --git a/reme/steps/benchmark/base/agentic_answer.py b/reme/steps/benchmark/base_agentic_answer.py similarity index 92% rename from reme/steps/benchmark/base/agentic_answer.py rename to reme/steps/benchmark/base_agentic_answer.py index 53c854f4..bcd5c8f6 100644 --- a/reme/steps/benchmark/base/agentic_answer.py +++ b/reme/steps/benchmark/base_agentic_answer.py @@ -2,14 +2,14 @@ import os -from ...base_step import BaseStep -from ...index._dedup import _ToolContextDedupMixin -from ....enumeration import ChunkEnum -from ....utils.counter import global_counter_inc +from ..base_step import BaseStep +from ..index._dedup import _ToolContextDedupMixin +from ...enumeration import ChunkEnum +from ...utils.counter import global_counter_inc class BaseAgenticAnswerStep(BaseStep): - """Base ReAct-agent answer step shared by BEAM and LongMemEval benchmarks. + """ReAct-agent answer implementation shared by benchmark plugins. Subclasses only need to set: TOOL_CONTEXT_PREFIX (str): prefix used to build the unique tool_context_id. @@ -18,7 +18,7 @@ class BaseAgenticAnswerStep(BaseStep): tool call via ``injected_job_kwargs``; override the attribute or the ``_injected_job_kwargs`` hook to customize. - And apply their own ``@R.register(...)`` decorator and docstring. + Concrete subclasses are registered by the plugin manifest. Inputs (from RuntimeContext): query (str, required): The question to answer. @@ -43,7 +43,7 @@ class BaseAgenticAnswerStep(BaseStep): ``INJECTED_JOB_KWARGS`` with per-request values derived from ``query``. When the runtime context carries a truthy ``compress_session`` flag, - session-transcript compression is enabled in ``search_v2_step`` by + session-transcript compression is enabled in the benchmark plugin's search Step by injecting a ``_search._compress.session`` marker plus the current ``query`` as the query-aware relevance filter. Default (falsy) leaves session chunks uncompressed. diff --git a/reme/steps/index/__init__.py b/reme/steps/index/__init__.py index b46dc9c5..b5dbb59e 100644 --- a/reme/steps/index/__init__.py +++ b/reme/steps/index/__init__.py @@ -12,7 +12,6 @@ from .init_changes import InitChangesStep from .optimize_index import OptimizeIndexStep from .reindex import ReindexStep from .search import SearchStep -from .search_v2 import SearchV2Step from .traverse import TraverseStep from .update_changes import ChangeApplyStep, UpdateCatalogStep, UpdateIndexStep from .vector_search import VectorSearchStep @@ -42,7 +41,6 @@ __all__ = [ "ReindexStep", "OptimizeIndexStep", "SearchStep", - "SearchV2Step", "TraverseStep", "UpdateCatalogStep", "UpdateIndexStep", diff --git a/tests/unit/test_beam_auto_memory.py b/tests/unit/test_beam_auto_memory.py deleted file mode 100644 index bddb3e79..00000000 --- a/tests/unit/test_beam_auto_memory.py +++ /dev/null @@ -1,128 +0,0 @@ -"""Unit tests for BEAM auto-memory segmented ingestion and line numbering.""" - -# pylint: disable=missing-class-docstring,missing-function-docstring,protected-access - -from agentscope.message import Msg - -from reme.components.runtime_context import RuntimeContext -from reme.steps.benchmark.beam.auto_memory import ( - BeamAutoMemoryStep, - split_turn_segments, -) - - -def _msg(role: str, words: int, created_at: str = "2024-03-01T09:00:00") -> Msg: - return Msg( - name=role, - role=role, - content=[{"type": "text", "text": " ".join(["w"] * words)}], - created_at=created_at, - ) - - -def _dialog(n_turns: int, words_per_msg: int) -> list[Msg]: - messages: list[Msg] = [] - for _ in range(n_turns): - messages.append(_msg("user", words_per_msg)) - messages.append(_msg("assistant", words_per_msg)) - return messages - - -class TestSplitTurnSegments: - def test_empty(self): - assert not split_turn_segments([], 100) - - def test_disabled_returns_single_segment(self): - messages = _dialog(3, 10) - segments = split_turn_segments(messages, 0) - assert len(segments) == 1 - assert segments[0] == (0, messages) - - def test_under_limit_single_segment(self): - messages = _dialog(3, 10) # 60 words total - segments = split_turn_segments(messages, 100) - assert len(segments) == 1 - assert segments[0][0] == 0 - assert segments[0][1] == messages - - def test_splits_at_turn_boundaries(self): - # 4 turns x 20 words each; limit 40 -> 2 turns per segment - messages = _dialog(4, 10) - segments = split_turn_segments(messages, 40) - assert len(segments) == 2 - offsets = [offset for offset, _ in segments] - assert offsets == [0, 4] - # Every segment starts with a user message and ends with an assistant - for _, segment in segments: - assert segment[0].role == "user" - assert segment[-1].role == "assistant" - # No message lost or duplicated, order preserved - flattened = [m for _, segment in segments for m in segment] - assert flattened == messages - - def test_never_splits_inside_a_turn(self): - # One turn alone exceeds the limit -> becomes its own oversized segment - messages = [ - _msg("user", 5), - _msg("assistant", 5), - _msg("user", 50), - _msg("assistant", 50), # 100-word turn > limit 60 - _msg("user", 5), - _msg("assistant", 5), - ] - segments = split_turn_segments(messages, 60) - assert [offset for offset, _ in segments] == [0, 2, 4] - assert [len(segment) for _, segment in segments] == [2, 2, 2] - - def test_offsets_are_original_indices(self): - messages = _dialog(5, 30) # 60 words per turn - segments = split_turn_segments(messages, 120) - # 2 turns per segment -> offsets 0, 4, 8 - assert [offset for offset, _ in segments] == [0, 4, 8] - for offset, segment in segments: - for i, msg in enumerate(segment): - assert msg is messages[offset + i] - - def test_multi_assistant_turn_stays_together(self): - messages = [ - _msg("user", 10), - _msg("assistant", 10), - _msg("assistant", 10), - _msg("user", 10), - _msg("assistant", 10), - ] - segments = split_turn_segments(messages, 30) - assert [offset for offset, _ in segments] == [0, 3] - assert len(segments[0][1]) == 3 - - -class TestFormatHistoryLineNumbers: - def _step(self, offset: int) -> BeamAutoMemoryStep: - step = BeamAutoMemoryStep(name="beam_auto_memory_step", backend="beam_auto_memory_step") - step.context = RuntimeContext( - session_id="beam_1M_1_batch1", - beam_line_offset=offset, - ) - return step - - def test_numbers_start_at_one_without_offset(self): - step = self._step(0) - history = step._format_history(_dialog(2, 3)) - assert "[L1 | user @" in history - assert "[L4 | assistant @" in history - assert "lines 1-4" in history - assert "session/dialog/beam_1M_1_batch1.jsonl" in history - - def test_numbers_use_original_file_offset(self): - step = self._step(40) - history = step._format_history(_dialog(2, 3)) - assert "[L41 | user @" in history - assert "[L44 | assistant @" in history - assert "lines 41-44" in history - assert "[L1 " not in history - - def test_build_messages_passes_msg_objects_through(self): - step = self._step(0) - messages = _dialog(2, 3) - rebuilt = step._build_messages(messages) - assert [m.id for m in rebuilt] == [m.id for m in messages] diff --git a/tests/unit/test_injected_job_kwargs.py b/tests/unit/test_injected_job_kwargs.py index 847b2757..d92482c5 100644 --- a/tests/unit/test_injected_job_kwargs.py +++ b/tests/unit/test_injected_job_kwargs.py @@ -230,7 +230,7 @@ async def test_auto_memory_update_scopes_tools_to_exact_note_path(tmp_path, monk def test_auto_memory_keeps_original_tool_names(): - """BEAM/LME configs define only the original jobs; no *_daily variants exist.""" + """Core auto-memory uses the original file tool names.""" step = AutoMemoryStep(name="auto_memory") assert step.create_tools == ["daily_write"] assert step.update_tools == ["read", "edit", "frontmatter_update", "write"] @@ -240,11 +240,7 @@ def test_auto_memory_create_prompts_match_upstream_date_arguments(): """Auto-memory prompts keep the upstream model-supplied date argument.""" from pathlib import Path - prompt_files = ( - Path("reme/steps/evolve/auto_memory.yaml"), - Path("reme/steps/benchmark/beam/auto_memory.yaml"), - Path("reme/steps/benchmark/lme/auto_memory.yaml"), - ) + prompt_files = (Path("reme/steps/evolve/auto_memory.yaml"),) for prompt_file in prompt_files: content = prompt_file.read_text(encoding="utf-8") assert "date={today}" in content or "`date`: {today}" in content or "`date`:{today}" in content @@ -253,7 +249,7 @@ def test_auto_memory_create_prompts_match_upstream_date_arguments(): def test_configs_define_original_jobs_without_daily_variants(): from reme.config import resolve_app_config - for config_name in ("default", "lme", "beam"): + for config_name in ("default",): config = resolve_app_config(config=config_name, log_config=False) jobs = config["jobs"] for name in ("read", "edit", "write", "frontmatter_update", "daily_write"): diff --git a/tests/unit/test_plugin.py b/tests/unit/test_plugin.py index a86aaa15..595b32e0 100644 --- a/tests/unit/test_plugin.py +++ b/tests/unit/test_plugin.py @@ -43,6 +43,22 @@ def _set_entry_points(monkeypatch, *entries): monkeypatch.setattr("reme.entry_point.metadata.entry_points", lambda: _FakeEntryPoints(entries)) +@pytest.mark.parametrize("name", ["lme", "beam"]) +def test_shared_benchmark_preset_is_builtin_but_plugin_aliases_and_backends_are_not(monkeypatch, name): + _set_entry_points(monkeypatch) + benchmark = _load_config("benchmark") + assert {"index_update", "digest_update", "read", "write"} <= benchmark["jobs"].keys() + assert {"search", "auto_memory", "agentic_answer", "answer_judge"}.isdisjoint(benchmark["jobs"]) + for alias in (name, f"{name}.yaml"): + with pytest.raises(FileNotFoundError, match="Config file not found"): + _load_config(alias) + assert R.get(ComponentEnum.STEP, f"{name}_auto_memory_step") is None + assert R.get(ComponentEnum.STEP, f"{name}_agentic_answer_step") is None + assert R.get(ComponentEnum.STEP, f"{name}_search_v2_step") is None + judge = "lme_answer_judge_step" if name == "lme" else "beam_rubric_judge_step" + assert R.get(ComponentEnum.STEP, judge) is None + + def test_plugin_application_defaults_are_below_application_config(): manager = PluginManager( [ diff --git a/tests/unit/test_search_step.py b/tests/unit/test_search_step.py index 67040f8b..65824d55 100644 --- a/tests/unit/test_search_step.py +++ b/tests/unit/test_search_step.py @@ -1,6 +1,8 @@ -"""Unit tests for SearchV2Step without embedding or LLM dependencies.""" +"""Unit tests for workspace search Steps without embedding or LLM dependencies.""" import asyncio +import importlib.util +from pathlib import Path from agentscope.message import Msg @@ -14,11 +16,17 @@ from reme.steps.index import ( Bm25SearchStep, ReadAllDraftStep, SearchStep, - SearchV2Step, VectorSearchStep, ) from reme.steps.index._source_format import ALL_RETURNED_MESSAGE, NO_RESULTS_MESSAGE +_SEARCH_V2_PATH = Path(__file__).parents[2] / "plugins" / "beam" / "src" / "reme_beam" / "search_v2.py" +_SEARCH_V2_SPEC = importlib.util.spec_from_file_location("reme_beam.search_v2", _SEARCH_V2_PATH) +assert _SEARCH_V2_SPEC is not None and _SEARCH_V2_SPEC.loader is not None +_SEARCH_V2_MODULE = importlib.util.module_from_spec(_SEARCH_V2_SPEC) +_SEARCH_V2_SPEC.loader.exec_module(_SEARCH_V2_MODULE) +SearchV2Step = _SEARCH_V2_MODULE.SearchV2Step + class FakeSearchStore(BaseFileStore): """Minimal file_store for SearchV2Step: static search results and empty graph links.""" diff --git a/typescript/README.md b/typescript/README.md index 19ec084c..9357adf1 100644 --- a/typescript/README.md +++ b/typescript/README.md @@ -2,145 +2,51 @@ [中文说明](./README_ZH.md) -`@agentscope-ai/reme` provides one shared ReMe HTTP client and host adapters for DeepSeek Harness and OpenClaw. Each -adapter uses its host's native lifecycle and tool interfaces; importing the root package does not load either host. +`@agentscope-ai/reme` connects DeepSeek Harness and OpenClaw to ReMe's local-first, file-native long-term memory. The package also exposes a host-independent ReMe HTTP client. -The package expects a running ReMe HTTP service with the `search`, `auto_memory`, and `auto_dream` jobs required by the -selected adapter: +![ReMe Status in DeepSeek Harness](./figures/dsh/reme-status-overview.png) + +## Capabilities + +- Injects memory guidance and provides explicit `reme_search` lookup. +- Captures completed conversations through background `auto_memory` batches. +- Runs optional daily `auto_dream` consolidation in the workspace timezone. +- Keeps durable memory in user-owned `daily` and `digest` Markdown files. +- Uses each host's native lifecycle, tools, settings, and shutdown hooks. +- Excludes plugin context and tool results from automatic memory capture. + +## Documentation + +| Host | English | 中文 | +| ---------------- | --------------------------- | ------------------------------------ | +| DeepSeek Harness | [Guide](./docs/dsh.md) | [使用指南](./docs/dsh.zh-CN.md) | +| OpenClaw | [Guide](./docs/openclaw.md) | [使用指南](./docs/openclaw.zh-CN.md) | + +## Quick start + +Start ReMe: ```bash +pip install "reme-ai[core]" reme start workspace_dir=/absolute/path/to/workspace ``` -The default endpoint is `http://127.0.0.1:2333`. All entries support `REME_URL`, or `REME_HOST` plus `REME_PORT`. -ReMe's HTTP service does not use API-key authentication. - -## Requirements - -- A running ReMe HTTP service with the jobs required by the selected adapter. -- Node.js `22.22.3+`, `24.15.0+`, or `25.9.0+` on the corresponding supported major-version line. -- DeepSeek Harness or OpenClaw only when using that host-specific entry; the root client has no host runtime dependency. - -## DeepSeek Harness - -Install the package as a DSH profile bundle: +Install the adapter for your host: ```bash +# DeepSeek Harness dsh plugin --profile web add @agentscope-ai/reme + +# OpenClaw +openclaw plugins install clawhub:@agentscope-ai/reme ``` -The bundle loads `@agentscope-ai/reme/dsh` in an isolated `remeMemory` realm. It injects durable memory guidance, -registers `reme_search`, submits completed main-agent turns to `auto_memory`, and runs the optional daily `auto_dream` -schedule. Recalled plugin context and tool results are excluded from automatic memory capture. +The default endpoint is `http://127.0.0.1:2333`. ReMe HTTP does not use API-key authentication, so keep it on loopback or another trusted network unless it is protected by a proxy. -Configure the bundle by replacing its row in the profile's `cordis.patch.yml`: - -```yaml -- id: reme-memory - config: - - id: reme-memory-runtime - name: "@agentscope-ai/reme/dsh" - config: - endpoint: http://127.0.0.1:2333 - language: zh - timezone: Asia/Shanghai - autoMemoryInterval: 5 - autoDreamEnabled: true - dreamCron: "0 23 * * *" -``` - -On the DSH Web profile, the same fields are available under **Settings → Plugins → Plugin configuration → ReMe -Memory**. Changes are stored in DSH's user settings document and apply to subsequent requests and captures. Changing -the daily dream controls reschedules the next run; changing the guidance language affects newly started sessions. -The test-only `dreamIntervalMs` value remains outside the user-settings section. - -| Option | Default | Meaning | -| --------------------- | ----------------------- | --------------------------------------------- | -| `endpoint` | `http://127.0.0.1:2333` | ReMe HTTP service URL | -| `language` | `en` | Memory guidance language: `en` or `zh` | -| `autoMemoryEnabled` | `true` | Capture completed main-agent turns | -| `autoMemoryInterval` | `5` | Submit after this many completed turns | -| `autoDreamEnabled` | `true` | Enable daily dream maintenance | -| `dreamCron` | `0 23 * * *` | Daily schedule in the workspace timezone | -| `dreamHint` | empty | Optional guidance sent to `auto_dream` | -| `rootAgentsOnly` | `true` | Exclude subagents from guidance and capture | -| `searchLimit` | `5` | Maximum results returned by `reme_search` | -| `requestTimeoutMs` | `10000` | Search request timeout | -| `backgroundTimeoutMs` | `3600000` | Automatic-memory and dream timeout | -| `shutdownTimeoutMs` | `5000` | Best-effort shutdown drain budget | -| `timezone` | `Asia/Shanghai` | IANA timezone used for batches and scheduling | - -The ReMe card reads the service's `health_check` and `status` jobs on demand. It shows the ReMe version, component -health, chunk/index counts, process RSS, and estimated component memory; it can also display the redacted `app_config` -response and trigger one `auto_dream` run. Diagnostics are refreshed when the card first opens or when the user asks, -not polled continuously. The page calls the configured ReMe HTTP endpoint from the local browser, so that service must -remain browser-reachable and allow the DSH origin. - -## OpenClaw - -OpenClaw `2026.7.1` or later can install the same package. The current SDK and -OpenClaw Gateway require Node.js `22.22.3+`, `24.15.0+`, or `25.9.0+` on their -respective major-version lines: - -```bash -openclaw plugins install @agentscope-ai/reme -``` - -Select `reme` for `plugins.slots.memory` when another memory plugin is active. The adapter uses OpenClaw's current -`before_prompt_build` hook, registers the `reme_search` action, injects durable memory guidance, and recalls relevant -memory before conversational root-agent runs. Completed user/assistant pairs are grouped into per-session, -date-consistent batches for `auto_memory`; failed batches are retained for retry and pending work is flushed within a -bounded Gateway shutdown budget. One plugin-owned daily schedule runs `auto_dream`. Recall is wrapped in -`` and marked as untrusted historical data. Cron, heartbeat, memory, overflow, and subagent runs do not -recall or capture conversational memory by default. - -OpenClaw plugin configuration accepts: - -| Option | Default | Meaning | -| --------------------- | ----------------------- | --------------------------------------------- | -| `endpoint` | `http://127.0.0.1:2333` | ReMe HTTP service URL | -| `language` | `en` | Memory guidance language: `en` or `zh` | -| `autoRecall` | `true` | Recall before conversational root-agent runs | -| `searchLimit` | `5` | Maximum search results | -| `recallMinScore` | `0` | Minimum search score | -| `autoMemoryEnabled` | `true` | Capture completed conversational turns | -| `autoMemoryInterval` | `5` | Submit after this many completed turns | -| `autoDreamEnabled` | `true` | Enable daily memory consolidation | -| `dreamCron` | `0 23 * * *` | Daily schedule in the workspace timezone | -| `dreamHint` | empty | Optional guidance sent to `auto_dream` | -| `rootAgentsOnly` | `true` | Exclude subagents from guidance and capture | -| `timezone` | `Asia/Shanghai` | IANA timezone used for batches and scheduling | -| `requestTimeoutMs` | `10000` | Recall and explicit search timeout | -| `backgroundTimeoutMs` | `3600000` | Automatic-memory and dream timeout | -| `shutdownTimeoutMs` | `5000` | Best-effort shutdown drain budget | - -OpenClaw's conversation-access and prompt-injection permissions remain host settings; enable them for ReMe when your -OpenClaw policy requires explicit grants. The adapter does not modify Gateway configuration. - -## Library entry - -Consumers that only need the transport can import the root package: +## Client library ```ts import { ReMeClient, formatReMeContext } from "@agentscope-ai/reme"; ``` -Host code is available only through `@agentscope-ai/reme/dsh` and `@agentscope-ai/reme/openclaw`. - -## Development - -```bash -cd typescript -npm ci -npm run format:check -npm run lint -npm run typecheck -npm test -npm run test:package -``` - -`npm pack` runs the TypeScript build and includes only `dist`, the DSH patch, the OpenClaw manifest, and the English and -Chinese READMEs. `npm run pack:clawhub -- ` creates the ClawHub artifact with the same runtime files -and the OpenClaw-only `README_OPENCLAW.md` and `README_OPENCLAW_ZH.md` content at the package README paths. - -Stable releases use npm's `latest` distribution tag; prereleases use `next`. +Host adapters are exported from `@agentscope-ai/reme/dsh` and `@agentscope-ai/reme/openclaw`. See the host guides for requirements, configuration, screenshots, troubleshooting, and release behavior. diff --git a/typescript/README_ZH.md b/typescript/README_ZH.md index 3e81cf29..5b108fac 100644 --- a/typescript/README_ZH.md +++ b/typescript/README_ZH.md @@ -1,136 +1,52 @@ # 面向 TypeScript Agent 的 ReMe -`@agentscope-ai/reme` 提供统一的 ReMe HTTP 客户端,以及 DeepSeek Harness(DSH)和 OpenClaw 适配器。每个适配器都使用宿主原生的生命周期与工具接口;导入包根入口不会加载任何宿主适配器。 +[English](./README.md) -使用前需要启动 ReMe HTTP 服务,并确保所选适配器需要的 `search`、`auto_memory` 和 `auto_dream` 任务可用: +`@agentscope-ai/reme` 将 DeepSeek Harness 和 OpenClaw 连接到 ReMe 本地优先、文件原生的长期记忆,并提供不依赖宿主的 ReMe HTTP 客户端。 + +![DeepSeek Harness 中的 ReMe 状态页](./figures/dsh/reme-status-overview.png) + +## 核心能力 + +- 注入长期记忆使用指引,并提供显式的 `reme_search` 检索。 +- 通过后台 `auto_memory` 批次沉淀已完成的对话。 +- 按 workspace 时区运行可选的每日 `auto_dream` 记忆整理。 +- 将持久记忆保存在用户拥有的 `daily` 和 `digest` Markdown 文件中。 +- 使用各宿主原生的生命周期、工具、设置和关停 Hook。 +- 自动记忆会排除插件上下文和工具结果,避免循环写回。 + +## 使用文档 + +| 宿主 | English | 中文 | +| ---------------- | --------------------------- | ------------------------------------ | +| DeepSeek Harness | [Guide](./docs/dsh.md) | [使用指南](./docs/dsh.zh-CN.md) | +| OpenClaw | [Guide](./docs/openclaw.md) | [使用指南](./docs/openclaw.zh-CN.md) | + +## 快速开始 + +启动 ReMe: ```bash +pip install "reme-ai[core]" reme start workspace_dir=/absolute/path/to/workspace ``` -默认服务地址为 `http://127.0.0.1:2333`。所有入口均支持 `REME_URL`,也支持组合使用 `REME_HOST` 和 `REME_PORT`。ReMe HTTP 服务不使用 API Key 认证。 - -## 环境要求 - -- 已启动 ReMe HTTP 服务,并提供所选适配器需要的 Job。 -- 对应受支持主版本线上的 Node.js `22.22.3+`、`24.15.0+` 或 `25.9.0+`。 -- 只有使用宿主专用入口时才需要 DeepSeek Harness 或 OpenClaw;包根 client 不依赖宿主 runtime。 - -## DeepSeek Harness - -将本包安装为 DSH profile bundle: +安装对应宿主的适配器: ```bash +# DeepSeek Harness dsh plugin --profile web add @agentscope-ai/reme + +# OpenClaw +openclaw plugins install clawhub:@agentscope-ai/reme ``` -安装后可在 **设置 → 插件 → 插件配置 → ReMe Memory** 中配置服务地址、记忆指引语言、自动记忆、每日记忆整理和超时时间,并查看服务健康状态。每日记忆整理和每日批次统一使用配置的 workspace 时区。 - -Bundle 会在隔离的 `remeMemory` realm 中加载 `@agentscope-ai/reme/dsh`,注入长期记忆指引,注册 -`reme_search`,将主 Agent 已完成的对话提交给 `auto_memory`,并按可选的每日计划运行 `auto_dream`。召回的 -plugin context 和工具结果不会再次进入自动记忆。 - -如需直接修改 profile,可以替换 `cordis.patch.yml` 中对应条目: - -```yaml -- id: reme-memory - config: - - id: reme-memory-runtime - name: "@agentscope-ai/reme/dsh" - config: - endpoint: http://127.0.0.1:2333 - language: zh - timezone: Asia/Shanghai - autoMemoryInterval: 5 - autoDreamEnabled: true - dreamCron: "0 23 * * *" -``` - -DSH Web profile 也可以在 **设置 → 插件 → 插件配置 → ReMe Memory** 中编辑同一组配置。设置会保存到 DSH -用户设置文档,并从后续请求和捕获开始生效;修改每日计划会重新调度下一次任务,修改语言只影响新会话。 -仅供测试的 `dreamIntervalMs` 不属于用户设置。 - -| 配置项 | 默认值 | 作用 | -| --------------------- | ----------------------- | ------------------------------- | -| `endpoint` | `http://127.0.0.1:2333` | ReMe HTTP 服务地址 | -| `language` | `en` | 记忆指引语言:`en` 或 `zh` | -| `autoMemoryEnabled` | `true` | 捕获主 Agent 已完成的对话 | -| `autoMemoryInterval` | `5` | 每完成多少轮提交一次 | -| `autoDreamEnabled` | `true` | 启用每日记忆整理 | -| `dreamCron` | `0 23 * * *` | workspace 时区下的每日计划 | -| `dreamHint` | 空字符串 | 传给 `auto_dream` 的可选指引 | -| `rootAgentsOnly` | `true` | 不为子 Agent 注入指引或捕获对话 | -| `searchLimit` | `5` | `reme_search` 返回结果上限 | -| `requestTimeoutMs` | `10000` | 搜索请求超时 | -| `backgroundTimeoutMs` | `3600000` | 自动记忆和 Auto Dream 超时 | -| `shutdownTimeoutMs` | `5000` | 退出时尽力排空任务的时间预算 | -| `timezone` | `Asia/Shanghai` | 每日批次和计划使用的 IANA 时区 | - -ReMe 配置卡会按需调用 `health_check` 和 `status`,显示 ReMe 版本、组件健康状态、chunk/index 数量、进程 RSS -与组件内存估算;它也可以显示脱敏后的 `app_config`,并手动触发一次 `auto_dream`。页面首次打开或用户主动刷新时才会 -更新诊断,不会持续轮询。浏览器必须能够访问所配置的 ReMe HTTP 地址,且 ReMe 服务需要允许 DSH 页面所在的 origin。 - -## OpenClaw - -OpenClaw `2026.7.1` 或更高版本可以直接安装本包。当前 SDK 和 Gateway 支持各主版本线上的 -Node.js `22.22.3+`、`24.15.0+` 或 `25.9.0+`: - -```bash -openclaw plugins install @agentscope-ai/reme -``` - -当其他记忆插件已启用时,请将 `plugins.slots.memory` 设为 `reme`。适配器使用最新的 -`before_prompt_build` Hook,注册 `reme_search` Action,并为根 Agent 注入记忆使用指引和相关历史。 -已完成的用户/助手消息按会话、日期分批提交给 `auto_memory`;失败批次会保留重试,Gateway 退出时会在有限时间内刷新。 -插件还会按 workspace 时区运行一份每日 `auto_dream` 计划。默认不会处理子 Agent、Cron、Heartbeat、Memory 或 Overflow 触发的运行。 - -召回内容包裹在 `` 中并标记为不可信历史数据。OpenClaw 插件配置如下: - -| 配置项 | 默认值 | 作用 | -| --------------------- | ----------------------- | ------------------------------------ | -| `endpoint` | `http://127.0.0.1:2333` | ReMe HTTP 服务地址 | -| `language` | `en` | 记忆指引语言:`en` 或 `zh` | -| `autoRecall` | `true` | 在根 Agent 对话运行前自动召回 | -| `searchLimit` | `5` | 搜索结果上限 | -| `recallMinScore` | `0` | 自动召回的最低搜索分数 | -| `autoMemoryEnabled` | `true` | 捕获已完成的对话 | -| `autoMemoryInterval` | `5` | 每完成多少轮提交一次 | -| `autoDreamEnabled` | `true` | 启用每日记忆整理 | -| `dreamCron` | `0 23 * * *` | workspace 时区下的每日计划 | -| `dreamHint` | 空字符串 | 传给 `auto_dream` 的可选指引 | -| `rootAgentsOnly` | `true` | 不为子 Agent 注入指引或捕获对话 | -| `timezone` | `Asia/Shanghai` | 每日批次和计划使用的 IANA 时区 | -| `requestTimeoutMs` | `10000` | 自动召回和显式搜索超时 | -| `backgroundTimeoutMs` | `3600000` | 自动记忆和 Auto Dream 超时 | -| `shutdownTimeoutMs` | `5000` | Gateway 退出时尽力排空任务的时间预算 | - -OpenClaw 的会话访问和 Prompt 注入权限仍由宿主侧配置;策略要求显式授权时,需要为 ReMe 开启相应权限。本适配器不会修改 -Gateway 配置。 +默认地址为 `http://127.0.0.1:2333`。ReMe HTTP 不使用 API Key 认证;除非前面有受保护的代理,否则应只监听 loopback 或其他可信网络。 ## 客户端库 -仅需要 HTTP 客户端时,可以从包根入口导入: - ```ts import { ReMeClient, formatReMeContext } from "@agentscope-ai/reme"; ``` -宿主适配器分别通过 `@agentscope-ai/reme/dsh` 和 `@agentscope-ai/reme/openclaw` 提供。 - -## 开发与发布检查 - -```bash -cd typescript -npm ci -npm run format:check -npm run lint -npm run typecheck -npm test -npm run test:package -``` - -`npm pack` 会先构建 TypeScript,并且只打包 `dist`、DSH patch、OpenClaw manifest 和中英文 README。 -`npm run pack:clawhub -- <输出目录>` 会使用相同的运行时文件生成 ClawHub 产物,并将仅包含 OpenClaw -说明的 `README_OPENCLAW.md` 和 `README_OPENCLAW_ZH.md` 放到包内 README 路径。 - -稳定版本使用 npm 的 `latest` distribution tag 发布,预发布版本使用 `next`。 +宿主适配器分别从 `@agentscope-ai/reme/dsh` 和 `@agentscope-ai/reme/openclaw` 导出。环境要求、完整配置、截图、排障和发布行为请查看对应的宿主文档。 diff --git a/typescript/docs/dsh.md b/typescript/docs/dsh.md new file mode 100644 index 00000000..cac2b204 --- /dev/null +++ b/typescript/docs/dsh.md @@ -0,0 +1,224 @@ +# ReMe plugin guide for DeepSeek Harness + +[中文说明](./dsh.zh-CN.md) + +This guide explains how to install, configure, and use `@agentscope-ai/reme` with DeepSeek Harness (DSH), including memory guidance injection, the `reme_search` tool, automatic memory, daily consolidation, and the ReMe Status page. + +The screenshots come from a real local integration test. DSH uses the `default` workspace, both the interface and ReMe guidance are set to English, and ReMe uses an isolated temporary workspace containing only the fictional Project Polaris test data. No `.env` values, API keys, or personal memories appear in the screenshots. + +## 1. How the plugin works + +When a DSH session starts, the plugin injects guidance that tells the root agent when and how to use long-term memory. It also registers the read-only `reme_search` tool. After a turn completes, the plugin can submit user and assistant messages to ReMe `auto_memory`; a daily schedule can run `auto_dream` to consolidate journal entries into durable personal knowledge. + +```text +New session + └─ Inject long-term-memory guidance + └─ Agent decides whether the request depends on history + └─ reme_search → ReMe search → daily / digest files + +Completed conversation + └─ Automatic-memory batch → ReMe auto_memory → daily files + └─ Scheduled consolidation → ReMe auto_dream → digest files +``` + +The DSH adapter injects **usage guidance**, not every historical memory. Relevant memories enter the conversation only when the agent calls `reme_search`. This keeps unrelated history out of the prompt and helps prevent historical content from being treated as new instructions. + +## 2. Requirements + +- ReMe is installed and its configuration exposes the `search`, `auto_memory`, and `auto_dream` jobs. +- DeepSeek Harness `0.1.2-rc.1` or later. +- Node.js `22.22.3+`, `24.15.0+`, or `25.9.0+` on the corresponding supported major-version line. +- The browser running DSH can reach the configured ReMe HTTP endpoint. Cross-machine deployments must also allow the DSH browser origin. + +The default ReMe endpoint is `http://127.0.0.1:2333`. ReMe HTTP does not use API-key authentication, so do not expose it directly to an untrusted network. + +## 3. Install and start + +### 3.1 Start ReMe + +```bash +reme start workspace_dir=/absolute/path/to/your/reme-workspace +``` + +For development and screenshots, use an isolated directory outside the repository, such as `/tmp/reme-dsh-demo`. Do not write runtime memory into the repository's `.reme/` directory. + +### 3.2 Install the DSH bundle + +Install the published package: + +```bash +dsh plugin --profile web add @agentscope-ai/reme +``` + +For local package development: + +```bash +cd /path/to/deepseek-harness +pnpm link /path/to/ReMe/typescript --workspace-root +dsh plugin --profile web add @agentscope-ai/reme +``` + +The package declares `dsh/cordis.patch.yml` through `package.json#dsh.bundle.patch`. The patch loads `@agentscope-ai/reme/dsh` and the Web client in an isolated `remeMemory` realm, following the DSH `0.1.2-rc.1` plugin protocol. + +### 3.3 Start DSH Web + +```bash +dsh web --no-open --port 3080 +``` + +Open the local URL printed by DSH and select the `default` workspace. If DSH enables an access token, use the authenticated URL from its startup output and do not copy the token into documentation or screenshots. + +## 4. Configure ReMe Memory + +Open **Settings → Plugins → Plugin configuration → ReMe Memory**. Save changes before starting the next session. Settings are stored in DSH's user settings document and apply to subsequent requests and captures. A language change affects new sessions; a schedule change immediately reschedules the next consolidation. + +![ReMe Memory plugin configuration](../figures/dsh/reme-memory-settings.png) + +| UI meaning | Configuration key | Default | Description | +| ---------------------- | --------------------- | ----------------------- | --------------------------------------------------------------- | +| Service URL | `endpoint` | `http://127.0.0.1:2333` | Absolute ReMe HTTP URL using `http` or `https`. | +| Guidance language | `language` | `en` | `en` or `zh`; controls guidance injected into new sessions. | +| Default search results | `searchLimit` | `5` | Default `reme_search` result limit, from 1 to 50. | +| Search timeout | `requestTimeoutMs` | `10000` | Search timeout in milliseconds, from 1,000 to 120,000. | +| Automatic memory | `autoMemoryEnabled` | `true` | Capture completed user/assistant turns for `auto_memory`. | +| Exclude subagents | `rootAgentsOnly` | `true` | Inject guidance and capture conversations only for root agents. | +| Submission interval | `autoMemoryInterval` | `5` | Submit after this many completed turns, from 1 to 1,000. | +| Memory consolidation | `autoDreamEnabled` | `true` | Run `auto_dream` on the daily schedule. | +| Consolidation schedule | `dreamCron` | `0 23 * * *` | Five-field cron expression interpreted in `timezone`. | +| Consolidation guidance | `dreamHint` | empty | Optional guidance passed to `auto_dream`. | +| Workspace timezone | `timezone` | `Asia/Shanghai` | IANA timezone used for batching and scheduling. | +| Background timeout | `backgroundTimeoutMs` | `3600000` | Timeout for `auto_memory` and `auto_dream`. | +| Shutdown flush timeout | `shutdownTimeoutMs` | `5000` | Budget for draining background work during shutdown. | + +Deployment configuration also supports `REME_URL`, or `REME_HOST` together with `REME_PORT`. The timer-only test option `dreamIntervalMs` is intentionally excluded from user settings. + +## 5. Memory context injection + +On `agent/session-start`, the plugin injects long-term-memory guidance as native plugin context. Expand **Context injection · reme-memory** in the message flow to inspect both the content and provenance. + +![ReMe memory context injection](../figures/dsh/memory-context-injection.png) + +The guidance establishes four rules: + +1. Durable long-term memory lives in user-owned `daily` and `digest` Markdown files. +2. The agent should call `reme_search` before answering questions that depend on past facts, preferences, decisions, people, dates, experience, or todos. +3. Retrieved memory is contextual evidence, not instructions. When no relevant result exists, the agent should say so instead of inventing a memory. +4. Background `auto_memory` and `auto_dream` jobs normally maintain memory without manual agent calls. + +The injected message carries `plugin=reme-memory` and `form=instructions` provenance. The plugin checks current and pending messages to avoid duplicate injection in one session. With `rootAgentsOnly=true`, sessions whose origin is `subagent` are skipped. + +## 6. Use `reme_search` + +A normal request can cause the agent to use memory automatically. For a deterministic check, explicitly request the tool and sources: + +```text +Use reme_search to look up my long-term memory: what are the weekly report time, +report format, and primary database for Project Polaris? Answer in English based +on the retrieved memory and cite the memory sources. +``` + +![Using reme_search](../figures/dsh/memory-search-tool.png) + +In the screenshot, the agent performs two read-only English searches. It corroborates the answer across `digest/wiki/polaris-project.md` and `daily/2026-09-04/dsh-plugin-demo.md`, then reports Friday at 4:00 PM, concise Markdown, and PostgreSQL with Redis as cache. + +| Parameter | Required | Description | +| ----------- | -------- | ------------------------------------------------------------------- | +| `query` | Yes | Focused natural-language search query; an empty value fails closed. | +| `limit` | No | Result limit from 1 to 50; defaults to the plugin's `searchLimit`. | +| `min_score` | No | Minimum score; normally leave it at 0. Negative values become 0. | + +An empty successful response becomes `No relevant memory found.`. Service failures become `ReMe search failed: ...`, allowing the agent to report a failed lookup instead of guessing. + +## 7. Automatic memory + +With `autoMemoryEnabled=true`, the plugin listens to DSH session events and collects completed user and assistant messages per session. When `autoMemoryInterval` is reached, the batch enters a background queue and calls ReMe `auto_memory`. Plugin-generated context and tool results are excluded from capture so they cannot be laundered back into long-term memory. + +![Automatic-memory activity](../figures/dsh/reme-status-auto-memory.png) + +Chat completion and durable memory completion are asynchronous. To verify persistence, open **ReMe Status → Auto Memory**, wait until running and queued tasks return to zero, and confirm that the latest submission is marked **Completed**. + +## 8. ReMe Status tabs + +Open **Settings → ReMe Status**. Full service diagnostics load when the page opens or the user refreshes them. While the page is visible, only the DSH plugin runtime counters refresh every 5 seconds. + +### 8.1 Overview + +![ReMe Status overview](../figures/dsh/reme-status-overview.png) + +Overview shows connectivity, ReMe version, endpoint, refresh time, automatic-memory and consolidation settings, process RSS, estimated component memory, active sessions, and queued turns. **Server configuration (redacted)** exposes a safe view of `app_config`. A green **Connected** badge confirms the health request, but optional component availability should still be checked under Components. + +### 8.2 Auto Memory + +![ReMe Status Auto Memory](../figures/dsh/reme-status-auto-memory.png) + +This tab reports active sessions, queued turns, running tasks, queued tasks, and the pipeline from conversation turns through the submission queue to long-term memory. Activity states include **Queued**, **Running**, **Completed**, **Failed**, and **Cancelled**. Activity is process-local diagnostic history; ReMe workspace files remain the durable source of truth. + +### 8.3 Memory Consolidation + +![ReMe Status Memory Consolidation](../figures/dsh/reme-status-auto-dream.png) + +This tab shows the next run, cron schedule, timezone, and most recent result. The flow is **Journal entries → Organize and connect → Personal knowledge base**. **Consolidate Memory Now** manually invokes `auto_dream`, which may call a model and modify workspace files. + +### 8.4 Components + +![ReMe Status Components](../figures/dsh/reme-status-components.png) + +Components displays health and resource usage for the file graph, file store, keyword index, and optional embedding store. An unconfigured embedding instance is not itself a failure. If a derived index is unhealthy, rebuild it from source Markdown instead of deleting or rewriting user memory. + +### 8.5 Journal + +![ReMe Status Journal](../figures/dsh/reme-status-journal.png) + +Journal browses the workspace's `daily` files. The left pane searches and selects files; the right pane previews paths, frontmatter metadata, and Markdown content. The list is capped at the newest 5,000 files. + +### 8.6 Personal Knowledge Base + +![ReMe Status Personal Knowledge Base](../figures/dsh/reme-status-knowledge.png) + +Personal Knowledge Base browses consolidated `digest` files. Journal entries preserve time-oriented source material, while digest documents hold stable, deduplicated knowledge for long-term recall. Wikilinks can preserve provenance back to the source journal entry. + +## 9. Troubleshooting + +### ReMe Status reports Unavailable + +- Confirm `reme start` is still running and verify the endpoint protocol, host, and port. +- In containers or cross-machine deployments, `127.0.0.1` refers to each machine separately; configure a browser-reachable address. +- Verify that ReMe allows the DSH Web origin. +- Increase `requestTimeoutMs` when the service legitimately needs more than ten seconds. + +### No memory context appears + +- Create a new session after changing `language`; existing sessions are not reinjected. +- Subagents are intentionally skipped when `rootAgentsOnly=true`. +- One session receives the guidance only once, deduplicated by provenance metadata. + +### The agent does not call `reme_search` + +- Explicitly ask it to use `reme_search`, base the answer on the result, and cite sources. +- Confirm the selected agent preset allows global tools. +- Check that the package was loaded through its DSH bundle patch, not merely installed as a dependency. + +### Search returns no useful result + +- Confirm the source file exists under Journal or Personal Knowledge Base. +- Use a focused query and adjust `limit` or `min_score` only when needed. +- Check file store, keyword index, and embedding-store health under Components. +- Rebuild derived indexes from source files; never rewrite source memory just to satisfy an index. + +### A completed chat has not appeared in Journal + +- Confirm `autoMemoryEnabled=true` and check whether `autoMemoryInterval` has been reached. +- Inspect queued, running, and failed states under Auto Memory. +- Allow for background completion. Shutdown only has the configured `shutdownTimeoutMs` drain budget. + +## 10. Validation represented by these screenshots + +The test used the DSH `default` workspace and verified: + +- DSH UI and ReMe guidance language set to English. +- English `reme-memory` plugin context with correct provenance. +- Two real `reme_search` calls returning consistent `daily` and `digest` evidence. +- Successful background `auto_memory` submission with no queued task remaining. +- Working Overview, Auto Memory, Memory Consolidation, Components, Journal, and Personal Knowledge Base tabs. + +DSH screenshots live in `typescript/figures/dsh/`. Future hosts can use parallel directories such as `typescript/figures/openclaw/`. diff --git a/typescript/docs/dsh.zh-CN.md b/typescript/docs/dsh.zh-CN.md new file mode 100644 index 00000000..04ca9245 --- /dev/null +++ b/typescript/docs/dsh.zh-CN.md @@ -0,0 +1,281 @@ +# ReMe DeepSeek Harness 插件使用指南 + +本文介绍如何在 DeepSeek Harness(DSH)中安装、配置和使用 `@agentscope-ai/reme`,并解释插件提供的长期记忆指引、`reme_search` 工具、自动记忆和 ReMe 状态页面。 + +本文截图来自一次真实的本地联调:DSH 选择 `default` 工作区,界面和 ReMe 指引均设置为 English,ReMe 使用隔离的临时 workspace,示例“Project Polaris”是为演示创建的英文数据。截图不包含 `.env` 内容、API Key 或真实个人记忆。 + +## 1. 插件做了什么 + +DSH 启动新会话时,插件向根 Agent 注入一段“如何使用长期记忆”的指引,并注册只读工具 `reme_search`。会话完成后,插件可以把用户与助手的对话按批次交给 ReMe `auto_memory`;每天还可以按计划调用 `auto_dream`,把日记进一步整理成个人知识。 + +一次典型的数据流如下: + +```text +新会话 + └─ 注入长期记忆使用指引 + └─ Agent 判断问题是否依赖历史信息 + └─ reme_search → ReMe search → daily / digest 文件 + +完成的对话 + └─ 自动记忆批次 → ReMe auto_memory → daily 文件 + └─ 定时记忆整理 → ReMe auto_dream → digest 文件 +``` + +重要区别:DSH 适配器在普通对话开始时注入的是**记忆使用指引**,不是把所有历史记忆自动塞进上下文。真正与问题相关的记忆由 Agent 调用 `reme_search` 获取。这能减少无关上下文,也避免把历史内容误当成新指令。 + +## 2. 环境要求 + +- ReMe Python 服务已安装,且配置中提供 `search`、`auto_memory` 和 `auto_dream` Job。 +- DeepSeek Harness `0.1.2-rc.1` 或更高版本。 +- Node.js `22.22.3+`、`24.15.0+` 或 `25.9.0+` 中的一条受支持版本线。 +- DSH 页面能够访问 ReMe HTTP 地址;跨机器部署时还要允许 DSH 页面所在的浏览器 Origin。 + +ReMe HTTP 服务默认监听 `http://127.0.0.1:2333`,不使用 API Key 认证。因此不建议未经网络隔离直接暴露到公网。 + +## 3. 安装与启动 + +### 3.1 启动 ReMe + +```bash +reme start workspace_dir=/absolute/path/to/your/reme-workspace +``` + +开发或截图测试时建议使用仓库外的独立目录,例如 `/tmp/reme-dsh-demo`,不要把运行时记忆写入 ReMe 仓库自身的 `.reme/`。 + +### 3.2 安装插件 + +安装已发布版本: + +```bash +dsh plugin --profile web add @agentscope-ai/reme +``` + +开发本仓库时,也可以把本地 TypeScript 包链接到 DSH workspace,然后仍按 DSH 的 bundle 协议加载: + +```bash +cd /path/to/deepseek-harness +pnpm link /path/to/ReMe/typescript --workspace-root +dsh plugin --profile web add @agentscope-ai/reme +``` + +包通过 `package.json` 的 `dsh.bundle.patch` 声明 `dsh/cordis.patch.yml`。该 patch 在独立 `remeMemory` realm 中加载运行时入口 `@agentscope-ai/reme/dsh` 和 Web 客户端入口 `@agentscope-ai/reme`,符合 DSH `0.1.2-rc.1` 的插件协议。 + +### 3.3 启动 DSH Web + +```bash +cd /path/to/deepseek-harness +set -a +source .env +set +a + +dsh web --no-open --port 3080 +``` + +打开输出中的本地地址,选择工作区 `default`。如果服务启用了访问令牌,使用启动日志给出的地址或按 DSH 提示完成认证;不要把令牌写进文档和截图。 + +## 4. ReMe Memory 配置 + +进入 **设置 → 插件 → 插件配置 → ReMe Memory**。修改后点击保存;设置存入 DSH 用户设置文档,并从后续请求或捕获开始生效。修改 `language` 只影响之后创建的新会话,修改每日计划会重新安排下一次整理。 + +![ReMe Memory 插件配置](../figures/dsh/reme-memory-settings.png) + +截图中的测试配置使用 `http://127.0.0.1:2333`、English 指引、默认搜索数量 5、搜索超时 10 秒,并启用了自动记忆和“Exclude subagents”。完整字段如下: + +| 界面含义 | 配置键 | 默认值 | 说明 | +| -------------------- | --------------------- | ----------------------- | -------------------------------------------------------------------------- | +| 服务地址 | `endpoint` | `http://127.0.0.1:2333` | DSH 访问的 ReMe HTTP 服务绝对地址,只支持 `http`/`https`。 | +| 记忆指引语言 | `language` | `en` | `en` 或 `zh`;决定新会话中注入的指引语言。 | +| 默认搜索数量 | `searchLimit` | `5` | `reme_search` 未传 `limit` 时的默认结果上限,范围 1–50。 | +| 搜索超时(毫秒) | `requestTimeoutMs` | `10000` | `search` 请求的超时,范围 1,000–120,000。 | +| 自动记录已完成的对话 | `autoMemoryEnabled` | `true` | 是否捕获完成的用户/助手回合并提交给 `auto_memory`。 | +| 排除子 Agent | `rootAgentsOnly` | `true` | 开启时,只为根 Agent 注入指引并捕获对话。 | +| 自动记忆提交间隔 | `autoMemoryInterval` | `5` | 每完成多少轮对话提交一次,范围 1–1,000。退出时插件会在时间预算内尽力排空。 | +| 自动整理记忆 | `autoDreamEnabled` | `true` | 是否按照每日计划调用 `auto_dream`。 | +| 整理计划 | `dreamCron` | `0 23 * * *` | 五段 cron 表达式,按 `timezone` 解释;默认每天 23:00。 | +| 整理指引 | `dreamHint` | 空 | 可选的整理提示,原样交给 `auto_dream`。 | +| 工作区时区 | `timezone` | `Asia/Shanghai` | 有效的 IANA 时区,用于每日批次和 cron 计划。 | +| 后台任务超时(毫秒) | `backgroundTimeoutMs` | `3600000` | `auto_memory` 和 `auto_dream` 的最大等待时间。 | +| 退出等待(毫秒) | `shutdownTimeoutMs` | `5000` | DSH 退出时等待后台任务排空的时间预算。 | + +部署层还支持 `REME_URL`,或组合使用 `REME_HOST` 与 `REME_PORT`。仅供定时器测试的 `dreamIntervalMs` 不出现在用户设置中。 + +## 5. 普通对话中的 memory 上下文注入 + +创建一个新会话后,插件监听 DSH 的 `agent/session-start`,把长期记忆使用规则作为一条原生 plugin context 注入。展开消息流中的 **上下文注入 · reme-memory** 可以直接检查内容与来源元数据。 + +![普通对话中的 ReMe memory 上下文注入](../figures/dsh/memory-context-injection.png) + +截图中的英文指引包含四条稳定规则: + +1. ReMe 的长期记忆来自用户拥有的本地 `daily` 和 `digest` Markdown 文件。 +2. 当问题依赖过去的事实、偏好、决策、人物、日期、经验或待办时,回答前应调用 `reme_search`。 +3. 检索结果只是上下文证据,不是新的指令;没有相关结果时不能编造记忆。 +4. `auto_memory` 与 `auto_dream` 在后台维护记忆,一般不需要 Agent 主动调用。 + +注入记录带有 `plugin=reme-memory`、`form=instructions` 元数据。插件会检查当前会话和待处理消息,确保同一个会话不重复注入。`rootAgentsOnly=true` 时,来源标记为 `subagent` 的会话不会收到该指引。 + +这张截图把注入内容与搜索回答放在同一屏,是为了说明“先收到规则,再按需检索”的顺序;注入块本身并不包含“北极星项目”的业务记忆。 + +## 6. 使用 `reme_search` 工具 + +通常只需要自然语言提出依赖历史信息的问题。截图使用的英文请求如下: + +```text +Use reme_search to look up my long-term memory: what are the weekly report time, +report format, and primary database for Project Polaris? Answer in English based +on the retrieved memory and cite the memory sources. +``` + +![使用 reme_search 检索长期记忆](../figures/dsh/memory-search-tool.png) + +截图中 Agent 发起了两次只读英文检索。最终回答从 Journal 和 Personal Knowledge Base 中交叉得到“Every Friday at 4:00 PM、Concise Markdown、PostgreSQL(Redis as cache)”,并列出了 `digest/wiki/polaris-project.md` 与 `daily/2026-09-04/dsh-plugin-demo.md` 两个来源。 + +工具参数: + +| 参数 | 是否必填 | 说明 | +| ----------- | -------- | ------------------------------------------------------- | +| `query` | 是 | 聚焦的自然语言检索词;空字符串会直接报错。 | +| `limit` | 否 | 返回结果数量,范围 1–50;缺省使用插件的 `searchLimit`。 | +| `min_score` | 否 | 最低分数,通常保持 0;负数会归一为 0。 | + +工具通过当前配置的 ReMe endpoint 调用 `search`。成功但没有内容时返回 `No relevant memory found.`;服务错误时返回 `ReMe search failed: ...`,便于 Agent 明确告诉用户检索失败,而不是猜测答案。 + +建议把查询拆成少量、语义明确的短句。如果答案涉及多个独立事实,可以像截图一样分别搜索,再让 Agent 对来源进行交叉核对。 + +## 7. 自动记忆如何工作 + +启用 `autoMemoryEnabled` 后,插件监听 DSH `session/event`,按会话收集完成的用户和助手消息。达到 `autoMemoryInterval` 后进入提交队列,后台调用 ReMe `auto_memory`。插件生成的上下文以及工具结果不会再次进入自动记忆,避免把指引或检索回显循环写回长期记忆。 + +截图测试把间隔临时设为 1;运行记录中可以看到多个英文测试会话形成的已完成提交: + +![自动记忆运行记录](../figures/dsh/reme-status-auto-memory.png) + +自动记忆是后台任务:聊天回答完成不代表磁盘写入已经在同一毫秒完成。需要确认时,打开 **ReMe 状态 → 自动记忆**,等待“运行中任务”和“排队任务”归零,并检查最近提交是否为“已完成”。 + +## 8. ReMe 状态页六个标签 + +进入 **Settings → ReMe Status**。页面首次打开或主动刷新时读取完整服务诊断;页面可见时,每 5 秒仅刷新 DSH 插件的运行时计数。这里的“标签”指页面顶部的六个 tab:Overview、Auto Memory、Memory Consolidation、Components、Journal、Personal Knowledge Base。 + +### 8.1 总览 + +![ReMe 状态总览](../figures/dsh/reme-status-overview.png) + +总览用于快速判断集成是否可用: + +- 顶部展示连接状态、ReMe 版本、endpoint 和最近刷新时间。 +- 自动记忆卡显示开关和提交间隔;记忆整理卡显示开关及下一次执行时间。 +- 进程内存是 ReMe 服务 RSS;组件内存是状态接口汇总的组件内存估算。 +- 活跃会话、待处理回合来自当前 DSH 插件运行时,不是历史累计值。 +- “服务配置(已脱敏)”展示 ReMe 返回的 `app_config` 安全视图,敏感字段不会原样显示。 +- “打开 ReMe”访问服务提供的 ReMe 页面;“ReMe 官网”打开项目网站。 + +绿色“已连接”意味着健康检查成功,不代表每个可选组件都已配置;组件详情应查看“组件”标签。 + +### 8.2 自动记忆 + +![ReMe 状态自动记忆](../figures/dsh/reme-status-auto-memory.png) + +该页把会话捕获状态拆成四个计数:活跃会话、待处理回合、运行中任务、排队任务。流程图表示“对话回合 → 提交队列 → 长期记忆”。 + +状态含义: + +- **空闲**:当前没有正在执行或排队的提交;不等于功能关闭。 +- **运行中/排队中**:批次正在请求 ReMe,或等待前面的任务完成。 +- **已完成**:ReMe 接受并完成该批次;行尾显示回合数和消息数。 +- **失败**:请求或 Job 执行失败,页面会显示最近错误。 +- **已取消**:关停期间任务未能在退出预算内完成。 + +运行记录保存在当前 DSH 进程的内存中,重启 DSH 后会重新计数;真正的长期数据仍以 ReMe workspace 文件为准。 + +### 8.3 记忆整理 + +![ReMe 状态记忆整理](../figures/dsh/reme-status-auto-dream.png) + +该页展示下一次整理时间、cron、时区和本次进程中的最近执行结果。流程为“日记记录 → 整理与关联 → 个人知识库”。点击 **立即整理** 会手动发起一次 `auto_dream`,可能调用模型并修改 ReMe workspace,应只在确实需要整理时使用。 + +cron 按插件配置的 IANA 时区解释。截图中的 `0 23 * * *` 与 `Asia/Shanghai` 表示每天北京时间 23:00。修改计划后无需重启 DSH,插件会重新调度。 + +### 8.4 组件 + +![ReMe 状态组件](../figures/dsh/reme-status-components.png) + +组件页展示 ReMe 的索引与存储基础设施。顶部 `3 / 3` 表示三个已配置的组件均健康;“向量存储未配置实例”是可选能力未启用,不等同于故障。 + +- **文件图谱**:显示节点、边、虚拟节点、待处理节点和内存占用,用于文件间关系与 wikilink。 +- **文件存储**:显示切分后的内容块、已生成向量的块数和内存占用。 +- **关键词索引**:显示已索引文档、词汇量和内存占用,为本次演示的文本检索提供能力。 +- **向量存储**:若启用,会显示模型、向量维度、缓存条目和内存;未配置时仍可使用服务端已有的其他搜索能力。 + +如果组件显示“需要处理”或“未启动”,优先查看 ReMe 服务日志和服务端配置,不要删除源 Markdown 文件来修复派生索引。 + +### 8.5 日记 + +![ReMe 状态日记](../figures/dsh/reme-status-journal.png) + +日记页浏览 ReMe workspace 的 `daily` 内容。左侧可以搜索和选择文件,右侧显示路径、frontmatter 元数据和 Markdown 正文。截图中除英文手工演示笔记外,还能看到英文搜索对话经 `auto_memory` 生成的条目。 + +列表最多显示最新 5,000 个文件。此页面用于查看,不改变“workspace 文件是持久事实源”的原则;索引、目录和缓存都应能够从这些文件重建。 + +### 8.6 个人知识库 + +![ReMe 状态个人知识库](../figures/dsh/reme-status-knowledge.png) + +个人知识库页浏览 `digest` 下经过整理的长期知识。布局与日记页一致:左侧文件列表,右侧元数据与内容预览。截图中的 `polaris-project.md` 汇总了长期偏好和技术决策,并通过 wikilink 指回原始日记来源。 + +日记更接近按天产生的原始记录,个人知识库更适合稳定、去重、可持续召回的知识。`reme_search` 可以同时从服务配置允许的这些来源中检索。 + +## 9. 日常使用建议 + +1. 先确认 **ReMe 状态 → 总览** 为“已连接”。 +2. 新建会话后,可在消息详情中展开 `reme-memory` 上下文确认指引已注入。 +3. 问题依赖历史事实时,明确要求 Agent 调用 `reme_search`,并要求列出来源。 +4. 对话结束后,到“自动记忆”确认批次完成,再到“日记”检查落盘内容。 +5. 用“记忆整理”的定时计划做日常归纳;只有需要立即验证时才手动触发。 +6. 定期查看“组件”健康,但修复索引时始终从 workspace 源文件重建,不要反向覆盖用户记忆。 + +## 10. 常见问题 + +### 状态页显示“连接失败” + +- 确认 `reme start` 仍在运行,并检查 `endpoint` 的协议、主机和端口。 +- DSH 与 ReMe 在容器或不同机器时,`127.0.0.1` 指向各自本机,需要改成浏览器可访问的地址。 +- 检查 ReMe 的 CORS 是否允许 DSH Web 的 Origin。 +- 超过 10 秒才响应时,按实际情况提高 `requestTimeoutMs`。 + +### 新会话没有看到上下文注入 + +- 保存 `language` 或开关设置后需要新建会话;已有会话不会补注入或更换语言。 +- 子 Agent 在 `rootAgentsOnly=true` 时会被主动跳过。 +- 同一会话只注入一次,插件会通过来源元数据去重。 + +### Agent 没有调用 `reme_search` + +- 用明确措辞说明问题依赖长期记忆,并要求“使用 `reme_search`、基于结果回答、标明来源”。 +- 检查当前 Agent 预设是否允许全局工具,以及消息详情中是否出现 ReMe 指引。 +- 检查插件是否按 DSH bundle patch 正常加载,而不只是把 npm 包安装到了依赖目录。 + +### 搜索无结果或结果过多 + +- 在“日记”和“个人知识库”中确认源文件确实存在。 +- 使用更聚焦的查询,必要时调整 `limit` 或 `min_score`。 +- 检查“组件”中的文件存储、关键词索引或向量存储状态。 +- 索引异常时重建派生状态,不要删除或改写源记忆来迁就索引。 + +### 聊天完成但日记还没更新 + +- 确认 `autoMemoryEnabled=true`,并了解 `autoMemoryInterval` 是达到多少个完成回合才提交。 +- 打开“自动记忆”检查待处理、排队、运行和失败状态。 +- 后台 Job 可能比聊天响应稍晚完成;DSH 退出时只有 `shutdownTimeoutMs` 的排空预算。 + +## 11. 本文联调结果 + +本次使用 `default` DSH 工作区完成了以下真实链路验证: + +- ReMe `0.4.1.11` 服务连接成功。 +- DSH 界面语言与 ReMe `Guidance language` 均已切换并保存为 English。 +- 新会话出现英文 `reme-memory` plugin context,来源元数据正确。 +- Agent 两次调用 `reme_search` 并从英文 `daily`、`digest` 返回一致答案。 +- 英文会话通过 `auto_memory` 完成后台提交,状态页无排队任务。 +- 总览、自动记忆、记忆整理、组件、日记、个人知识库六个标签均能读取并展示数据。 + +DSH 截图统一放在 `typescript/figures/dsh/`,本文位于 `typescript/docs/`。后续其他宿主的截图可以使用并列目录,例如 `typescript/figures/openclaw/`。运行时演示记忆位于仓库外的临时 workspace,不属于项目产物。 diff --git a/typescript/README_OPENCLAW.md b/typescript/docs/openclaw.md similarity index 99% rename from typescript/README_OPENCLAW.md rename to typescript/docs/openclaw.md index 8ee632c0..de9a616a 100644 --- a/typescript/README_OPENCLAW.md +++ b/typescript/docs/openclaw.md @@ -1,6 +1,6 @@ # ReMe memory for OpenClaw -[中文说明](./README_ZH.md) +[中文说明](./openclaw.zh-CN.md) ReMe gives OpenClaw file-native long-term memory while keeping durable memory in a workspace you own. The plugin uses OpenClaw's native lifecycle, hooks, tools, and memory slot. diff --git a/typescript/README_OPENCLAW_ZH.md b/typescript/docs/openclaw.zh-CN.md similarity index 99% rename from typescript/README_OPENCLAW_ZH.md rename to typescript/docs/openclaw.zh-CN.md index d518b506..01444319 100644 --- a/typescript/README_OPENCLAW_ZH.md +++ b/typescript/docs/openclaw.zh-CN.md @@ -1,6 +1,6 @@ # OpenClaw 的 ReMe 长期记忆插件 -[English](./README.md) +[English](./openclaw.md) ReMe 为 OpenClaw 提供文件原生的长期记忆,并将持久记忆保存在用户拥有的 workspace 中。插件使用 OpenClaw 原生的生命周期、Hook、工具接口和 memory slot。 diff --git a/typescript/figures/dsh/memory-context-injection.png b/typescript/figures/dsh/memory-context-injection.png new file mode 100644 index 00000000..c105deb9 Binary files /dev/null and b/typescript/figures/dsh/memory-context-injection.png differ diff --git a/typescript/figures/dsh/memory-search-tool.png b/typescript/figures/dsh/memory-search-tool.png new file mode 100644 index 00000000..42b8b114 Binary files /dev/null and b/typescript/figures/dsh/memory-search-tool.png differ diff --git a/typescript/figures/dsh/reme-memory-settings.png b/typescript/figures/dsh/reme-memory-settings.png new file mode 100644 index 00000000..fceeeede Binary files /dev/null and b/typescript/figures/dsh/reme-memory-settings.png differ diff --git a/typescript/figures/dsh/reme-status-auto-dream.png b/typescript/figures/dsh/reme-status-auto-dream.png new file mode 100644 index 00000000..d9e197c8 Binary files /dev/null and b/typescript/figures/dsh/reme-status-auto-dream.png differ diff --git a/typescript/figures/dsh/reme-status-auto-memory.png b/typescript/figures/dsh/reme-status-auto-memory.png new file mode 100644 index 00000000..0ddb845f Binary files /dev/null and b/typescript/figures/dsh/reme-status-auto-memory.png differ diff --git a/typescript/figures/dsh/reme-status-components.png b/typescript/figures/dsh/reme-status-components.png new file mode 100644 index 00000000..0ee26116 Binary files /dev/null and b/typescript/figures/dsh/reme-status-components.png differ diff --git a/typescript/figures/dsh/reme-status-journal.png b/typescript/figures/dsh/reme-status-journal.png new file mode 100644 index 00000000..2775ab79 Binary files /dev/null and b/typescript/figures/dsh/reme-status-journal.png differ diff --git a/typescript/figures/dsh/reme-status-knowledge.png b/typescript/figures/dsh/reme-status-knowledge.png new file mode 100644 index 00000000..d219c852 Binary files /dev/null and b/typescript/figures/dsh/reme-status-knowledge.png differ diff --git a/typescript/figures/dsh/reme-status-overview.png b/typescript/figures/dsh/reme-status-overview.png new file mode 100644 index 00000000..df0dcf7b Binary files /dev/null and b/typescript/figures/dsh/reme-status-overview.png differ diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 911e3f93..a36cc88b 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -12,13 +12,13 @@ "typebox": "1.3.19" }, "devDependencies": { - "@deepseek-ai/cordis": "4.0.1", - "@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.8", - "@deepseek-ai/dsh-llm": "0.1.0-rc.8", - "@deepseek-ai/dsh-settings": "0.1.0-rc.8", - "@deepseek-ai/dsh-tools": "0.1.0-rc.8", - "@deepseek-ai/dsh-typert-protocol": "0.1.0-rc.8", - "@deepseek-ai/schemastery": "3.18.1", + "@deepseek-ai/cordis": "4.0.2", + "@deepseek-ai/dsh-client-ui-primitives": "0.1.2-rc.1", + "@deepseek-ai/dsh-llm": "0.1.2-rc.1", + "@deepseek-ai/dsh-settings": "0.1.2-rc.1", + "@deepseek-ai/dsh-tools": "0.1.2-rc.1", + "@deepseek-ai/dsh-typert-protocol": "0.1.2-rc.1", + "@deepseek-ai/schemastery": "3.18.2", "@eslint/js": "9.39.4", "@types/node": "^22.15.0", "@types/react": "~18.3.1", @@ -36,13 +36,13 @@ "node": "^22.22.3 || ^24.15.0 || >=25.9.0" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.8", - "@deepseek-ai/dsh-llm": "^0.1.0-rc.8", - "@deepseek-ai/dsh-settings": "^0.1.0-rc.8", - "@deepseek-ai/dsh-tools": "^0.1.0-rc.8", - "@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.8", - "@deepseek-ai/schemastery": "^3.18.1", + "@deepseek-ai/cordis": "^4.0.2", + "@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-rc.1", + "@deepseek-ai/dsh-llm": "^0.1.2-rc.1", + "@deepseek-ai/dsh-settings": "^0.1.2-rc.1", + "@deepseek-ai/dsh-tools": "^0.1.2-rc.1", + "@deepseek-ai/dsh-typert-protocol": "^0.1.2-rc.1", + "@deepseek-ai/schemastery": "^3.18.2", "openclaw": ">=2026.7.1" }, "peerDependenciesMeta": { @@ -313,21 +313,21 @@ } }, "node_modules/@deepseek-ai/cordis": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@deepseek-ai/cordis/-/cordis-4.0.1.tgz", - "integrity": "sha512-YBdskTU2Po1kru3GgcUWUbkTsPMA9LkSQDAY8rBkFJeajdgcQad3QPJZE26JyK99Xb6HaASvoXg2DSUTeN/0Nw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/cordis/-/cordis-4.0.2.tgz", + "integrity": "sha512-asOnXP1TzFSFQlHb1iegDZp0z/8WD1c7YNrwJR/Tx2bzNuMXfcekE/I67Iv6SQXeLB4csxqCngzQKANP7gdw0g==", "dev": true, "license": "MIT", "dependencies": { - "@deepseek-ai/cosmokit": "^1.8.2", + "@deepseek-ai/cosmokit": "^1.8.3", "@standard-schema/spec": "^1.1.0" }, "bin": { "cordis": "bin.js" }, "peerDependencies": { - "@deepseek-ai/cordis-plugin-include": "^1.0.6", - "@deepseek-ai/cordis-plugin-loader": "^1.0.2" + "@deepseek-ai/cordis-plugin-include": "^1.0.7", + "@deepseek-ai/cordis-plugin-loader": "^1.0.3" }, "peerDependenciesMeta": { "@deepseek-ai/cordis-plugin-include": { @@ -339,58 +339,44 @@ } }, "node_modules/@deepseek-ai/cosmokit": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@deepseek-ai/cosmokit/-/cosmokit-1.8.2.tgz", - "integrity": "sha512-muBOKtSrUKU5m/xpq8ZXWL6hQ/jgd4PhU2PqH97bcxIiLEJfNwZOGQEx4t/aS/GgxRAR+ra9pMHPMtTHU4sqqA==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@deepseek-ai/cosmokit/-/cosmokit-1.8.3.tgz", + "integrity": "sha512-qBo+ronVM6Eu2WNVJXi8JcMiqZ19T9BRIpV+5qJUFPXjGH/Z0QKcQMC/IZJ7L394YTOtJgcovbk9qP0w2GsBXQ==", "dev": true, "license": "MIT" }, "node_modules/@deepseek-ai/dsh-agent": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-agent/-/dsh-agent-0.1.0-rc.8.tgz", - "integrity": "sha512-fOl46ZvzoYHalrlyfOJAHNhzyKeVDTUnZZKNSJEr9GY/98WJTjcV21agVEofTXldkJ7a/ws2HpJkVNJVFMtslg==", + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-agent/-/dsh-agent-0.1.2-rc.1.tgz", + "integrity": "sha512-lfaqN34vUCWvbn1kJVHrhfJ6Dvt1HDHCm33ZCpmKkl07/5q6FxWqVv6rOdVX5QnM/9xz/uYiN0nQwUtBg4+Skg==", "dev": true, "license": "MIT", "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8", - "@deepseek-ai/dsh-llm": "^0.1.0-rc.8", - "@deepseek-ai/dsh-scope": "^0.1.0-rc.8", - "@deepseek-ai/dsh-session": "^0.1.0-rc.8", - "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.8", - "@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.8" - } - }, - "node_modules/@deepseek-ai/dsh-attachment": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-attachment/-/dsh-attachment-0.1.0-rc.8.tgz", - "integrity": "sha512-cCrg4WWiav7pGtbdU8dJTpAG28cPnkWVB2YPOrlBCgBbhQziGcG9Dv67Ti6L3PI4OQKC55gYBFinQghFO/7FGg==", - "dev": true, - "license": "MIT", - "peer": true, - "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-brand": "^0.1.0-rc.8", - "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8" + "@deepseek-ai/cordis": "^4.0.2", + "@deepseek-ai/dsh-invariants": "^0.1.2-rc.1", + "@deepseek-ai/dsh-llm": "^0.1.2-rc.1", + "@deepseek-ai/dsh-scope": "^0.1.2-rc.1", + "@deepseek-ai/dsh-session": "^0.1.2-rc.1", + "@deepseek-ai/dsh-session-projection": "^0.1.2-rc.1", + "@deepseek-ai/dsh-system-prompt": "^0.1.2-rc.1", + "@deepseek-ai/dsh-typert-protocol": "^0.1.2-rc.1" } }, "node_modules/@deepseek-ai/dsh-brand": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-brand/-/dsh-brand-0.1.0-rc.8.tgz", - "integrity": "sha512-402aUAfHxIZJrArBV4gDf0I/A/69A/By26FX6HTIOyFmCnB4wUBKh8jnglz1CorKWdZy8AKldAhh8HQhQEbGOQ==", + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-brand/-/dsh-brand-0.1.2-rc.1.tgz", + "integrity": "sha512-y2RHIag3kzLdY8kTBuSUSTsKzWPscJrysNObXTiNM7stXLpa3jXpshsB1caqGzNx50vYcmHeN1gFVECgx3NZHg==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8" + "@deepseek-ai/cordis": "^4.0.2" } }, "node_modules/@deepseek-ai/dsh-client-ui-primitives": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-primitives/-/dsh-client-ui-primitives-0.1.0-rc.8.tgz", - "integrity": "sha512-P3/n4jWT5kp4kTH67h+nmLu8LhKl9KsbFn8plumQFPXKGj2ythwmVoK3Nc329ANcYlxKXVtnoLPwYrTxkSeIpA==", + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-client-ui-primitives/-/dsh-client-ui-primitives-0.1.2-rc.1.tgz", + "integrity": "sha512-4gZS+tXAHbSxAjP91+lENpxKb49XVKcO80m7ATNNgD6lJHOwbuWVGv9IwEnmQ5ebZvI8Yrp8C1J+jDe1pB9jXw==", "dev": true, "license": "MIT", "dependencies": { @@ -416,184 +402,223 @@ "shiki": "^4.3.1" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8" + "@deepseek-ai/cordis": "^4.0.2" } }, "node_modules/@deepseek-ai/dsh-code-runtime": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-code-runtime/-/dsh-code-runtime-0.1.0-rc.8.tgz", - "integrity": "sha512-6ZKyEE7dHH1UkKe5b0lg18Byww/7ocZyrNMVjAoUWEWp5i96Brf9Eavu9q3zepBMDMZJErCfV0pyt7KDhBJSig==", + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-code-runtime/-/dsh-code-runtime-0.1.2-rc.1.tgz", + "integrity": "sha512-dv5UszPJb/AFcarZc2c6h5z/X80bsrSd3bfIOugwNVclSI6P+L3ixseoZKgyks7T0Ap0h6ngXGk2WeoUkmYxnQ==", "dev": true, "license": "MIT", "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8" + "@deepseek-ai/cordis": "^4.0.2" } }, "node_modules/@deepseek-ai/dsh-invariants": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-invariants/-/dsh-invariants-0.1.0-rc.8.tgz", - "integrity": "sha512-u0lYqyxOYwfsVnbsfGXZos5vFvA4cqFnBEW3/ezgljNwkYwzeUP/Y5wjPnQjP+ZzBn3CnVeIF6s2N2Vk3iA5mQ==", + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-invariants/-/dsh-invariants-0.1.2-rc.1.tgz", + "integrity": "sha512-zSjuFJYcEB94kPwn7SicqAV1dfVWZkjt1I8luDboxtjfp5CN6ziwJhLv3KaB+Dh9tgkyGY4mvo886W/HhE9L9g==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/schemastery": "^3.18.2" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1" + "@deepseek-ai/cordis": "^4.0.2" } }, "node_modules/@deepseek-ai/dsh-llm": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-llm/-/dsh-llm-0.1.0-rc.8.tgz", - "integrity": "sha512-hf6RmX2Stn1UtzpktSHQLkIktQkBm9i/3yFfJlbGSkwyw5Cv9sTYJbyoHzsSnnh6i8nRRcMcBhvMgm23I00oIw==", + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-llm/-/dsh-llm-0.1.2-rc.1.tgz", + "integrity": "sha512-7VYsha5AXsVLnsAwYJffWXz9bwUbElw8i5N8tlTSdai9Bupk3sMbsotzPf8ZbsuGAxQYErahMwQwgGEu4qZO6g==", "dev": true, "license": "MIT", "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/dsh-brand": "^0.1.2-rc.1", + "@deepseek-ai/dsh-timeout": "^0.1.2-rc.1", + "@deepseek-ai/dsh-typert-protocol": "^0.1.2-rc.1", + "@deepseek-ai/dsh-util-crypto": "^0.1.2-rc.1", + "@deepseek-ai/dsh-util-values": "^0.1.2-rc.1", + "@deepseek-ai/schemastery": "^3.18.2", + "zod": "^4.4.3" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-attachment": "^0.1.0-rc.8", - "@deepseek-ai/dsh-brand": "^0.1.0-rc.8", - "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8", - "@deepseek-ai/dsh-timeout": "^0.1.0-rc.8" + "@deepseek-ai/cordis": "^4.0.2" } }, "node_modules/@deepseek-ai/dsh-scope": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-scope/-/dsh-scope-0.1.0-rc.8.tgz", - "integrity": "sha512-/o+PxkVV2zUy9lYlMFr4TWSb+ESW4DE+fI891yIbsLzOJdFgfbmmLpGrlWgnz8/unIGJZxjeXdwyppV04Z277Q==", + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-scope/-/dsh-scope-0.1.2-rc.1.tgz", + "integrity": "sha512-GHwbzWrGbmE0ZGzAZsXBudpyCM9ENCpXxlKODIaPFbIWhRePh6nH2jW47TAOqCIsPPedWC2p3w8Tx5WAa3I/2A==", "dev": true, "license": "MIT", "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8" + "@deepseek-ai/cordis": "^4.0.2", + "@deepseek-ai/dsh-invariants": "^0.1.2-rc.1" } }, "node_modules/@deepseek-ai/dsh-session": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session/-/dsh-session-0.1.0-rc.8.tgz", - "integrity": "sha512-Qs5Mgn32WPPVB4rKHjsZBgLOkh0TQurUAcc9GaDVudK4dUkpno2HDh++qu/PrwRA3CZ/iwPAoFHCpB3AMMaoLg==", + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session/-/dsh-session-0.1.2-rc.1.tgz", + "integrity": "sha512-jRGNPTbQcvIx1F2MVmmkoiHLgpC3Btqo1wkl/3JDLlOWRVWKOuaC+5fQiMrFPOYfno6YiX/c2UvmA0td8qB92w==", "dev": true, "license": "MIT", "peer": true, + "dependencies": { + "@deepseek-ai/dsh-brand": "^0.1.2-rc.1", + "@deepseek-ai/dsh-llm": "^0.1.2-rc.1", + "@deepseek-ai/dsh-util-values": "^0.1.2-rc.1" + }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-brand": "^0.1.0-rc.8", - "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8", - "@deepseek-ai/dsh-llm": "^0.1.0-rc.8", - "@deepseek-ai/dsh-scope": "^0.1.0-rc.8", - "@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.8" + "@deepseek-ai/cordis": "^4.0.2", + "@deepseek-ai/dsh-scope": "^0.1.2-rc.1" + } + }, + "node_modules/@deepseek-ai/dsh-session-projection": { + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session-projection/-/dsh-session-projection-0.1.2-rc.1.tgz", + "integrity": "sha512-qVWlIzPm4u9vzQ5G/QtE6jeSJ3b3b42hWVJj664BurEZlHbAfdqkhYc1QsoBU+5k5kUEJFHh07z8HxuXA9kTnQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "zod": "^4.4.3" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "^4.0.2", + "@deepseek-ai/dsh-session": "^0.1.2-rc.1" } }, "node_modules/@deepseek-ai/dsh-settings": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-settings/-/dsh-settings-0.1.0-rc.8.tgz", - "integrity": "sha512-pqbMoiSP+Ieww9/4gcHYXcBCDlno1zYDu2OPq+5xdZ+McnK+5YBtHsiRpnR9cHQxGTH7lkfIv+adkOueQW0utg==", + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-settings/-/dsh-settings-0.1.2-rc.1.tgz", + "integrity": "sha512-wMmJ2w5S6I7hgmgPppuAVemltEcTmAa8gvii7fk4T2KGVCuBYgig8xPwQ8Lp2ukLZSzzE6ZVCQxhdYZyJX/USA==", "dev": true, "license": "MIT", + "dependencies": { + "@deepseek-ai/dsh-util-values": "^0.1.2-rc.1" + }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-brand": "^0.1.0-rc.8", - "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8", - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/cordis": "^4.0.2", + "@deepseek-ai/dsh-brand": "^0.1.2-rc.1", + "@deepseek-ai/dsh-invariants": "^0.1.2-rc.1", + "@deepseek-ai/dsh-session": "^0.1.2-rc.1", + "@deepseek-ai/schemastery": "^3.18.2" } }, "node_modules/@deepseek-ai/dsh-system-prompt": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-system-prompt/-/dsh-system-prompt-0.1.0-rc.8.tgz", - "integrity": "sha512-yk8Z85rzX2EwpKNWA5i+K3I3mqsIIUc9niwbDpXO8vlHvpC2qR3RNHjJIJN2EZYDqfSzTVtkbxHFsuMzDXIbew==", + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-system-prompt/-/dsh-system-prompt-0.1.2-rc.1.tgz", + "integrity": "sha512-7W93PZKIk4CHvjZGgLIxrrEKpk+6t8nQXje1vKR5vG73dfXH0t1MJ5WWH/fngadYc7c6KzM84ihQ66tJqJqyNQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/schemastery": "^3.18.2" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8", - "@deepseek-ai/dsh-llm": "^0.1.0-rc.8", - "@deepseek-ai/dsh-scope": "^0.1.0-rc.8" + "@deepseek-ai/cordis": "^4.0.2", + "@deepseek-ai/dsh-invariants": "^0.1.2-rc.1", + "@deepseek-ai/dsh-llm": "^0.1.2-rc.1", + "@deepseek-ai/dsh-scope": "^0.1.2-rc.1" } }, "node_modules/@deepseek-ai/dsh-timeout": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-timeout/-/dsh-timeout-0.1.0-rc.8.tgz", - "integrity": "sha512-RwOC/qHribE6b+LStAO7aAgweefLE5sqDO7Uz6/mnTDyJxCt7HNQnE/Op8sAvRa33d752aUkPfO9S0mgzVAoFQ==", + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-timeout/-/dsh-timeout-0.1.2-rc.1.tgz", + "integrity": "sha512-llz11yxWeJB8suKCa6hRLjNBWVaPn3Fbd0XoBS0bkZLhPFzO1mDorx/XMca2GhI7XF9A86CURQcDt+aGjjvqJA==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8" + "@deepseek-ai/cordis": "^4.0.2" } }, "node_modules/@deepseek-ai/dsh-tools": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-tools/-/dsh-tools-0.1.0-rc.8.tgz", - "integrity": "sha512-tvuGAVUS4yoPW9zH7CGWJgMlAzGjfu2UlRiricldlcwSKAaJZPZx/K/YfTivUyDTER6aK1DJUnIpVG7RD/DoHQ==", + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-tools/-/dsh-tools-0.1.2-rc.1.tgz", + "integrity": "sha512-W9kUio00s7WbM8kEwniyd4hfb3CeUxVczsjXbOcKtakfiTywNLeyRWkpx2fvwRAH2rBfg3qCgczVfS0DOw1csg==", "dev": true, "license": "MIT", "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/dsh-brand": "^0.1.2-rc.1", + "@deepseek-ai/dsh-util-values": "^0.1.2-rc.1", + "@deepseek-ai/schemastery": "^3.18.2" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.0-rc.8", - "@deepseek-ai/dsh-code-runtime": "^0.1.0-rc.8", - "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8", - "@deepseek-ai/dsh-llm": "^0.1.0-rc.8", - "@deepseek-ai/dsh-scope": "^0.1.0-rc.8", - "@deepseek-ai/dsh-session": "^0.1.0-rc.8", - "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.8", - "@deepseek-ai/dsh-user-approval": "^0.1.0-rc.8" + "@deepseek-ai/cordis": "^4.0.2", + "@deepseek-ai/dsh-agent": "^0.1.2-rc.1", + "@deepseek-ai/dsh-code-runtime": "^0.1.2-rc.1", + "@deepseek-ai/dsh-invariants": "^0.1.2-rc.1", + "@deepseek-ai/dsh-llm": "^0.1.2-rc.1", + "@deepseek-ai/dsh-scope": "^0.1.2-rc.1", + "@deepseek-ai/dsh-session": "^0.1.2-rc.1", + "@deepseek-ai/dsh-system-prompt": "^0.1.2-rc.1", + "@deepseek-ai/dsh-user-approval": "^0.1.2-rc.1" } }, "node_modules/@deepseek-ai/dsh-typert-protocol": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-typert-protocol/-/dsh-typert-protocol-0.1.0-rc.8.tgz", - "integrity": "sha512-scwEAefRBL7gRpaXiEdiHRMvlr8dLmUbiEvy0LOAX/8X3FZBd/5tHQ/8lCppAQNTDZMI/LSiSMpu5XrFId+7cQ==", + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-typert-protocol/-/dsh-typert-protocol-0.1.2-rc.1.tgz", + "integrity": "sha512-vBWD0NpriPd96ltbWaAw+iKeWFIEjiAcCWYtmqEcV1Ms+FaV02usjjm7LCmhSQhMZpBSBc7EZUX0spGlpjI1bA==", "dev": true, "license": "MIT", "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8" + "@deepseek-ai/cordis": "^4.0.2" } }, "node_modules/@deepseek-ai/dsh-user-approval": { - "version": "0.1.0-rc.8", - "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-user-approval/-/dsh-user-approval-0.1.0-rc.8.tgz", - "integrity": "sha512-6SFCxp7dgNFEyzQIcs5i9C+5djgXsAeV0iKmZUCLlcIeVM1chcCMHyGcoXlw8yyB14qFMrMs+TPvhvpu2WWxAw==", + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-user-approval/-/dsh-user-approval-0.1.2-rc.1.tgz", + "integrity": "sha512-oYkLE4a/TwqVNi299pUf/QP1Yku6KScdUCTUyYbjaRBcN+/pXPpHikl2aoE2thH9D0uT/Kj6T2kz+wDDcFd9Xg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@deepseek-ai/schemastery": "^3.18.1" + "@deepseek-ai/schemastery": "^3.18.2" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-agent": "^0.1.0-rc.8", - "@deepseek-ai/dsh-brand": "^0.1.0-rc.8", - "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8", - "@deepseek-ai/dsh-llm": "^0.1.0-rc.8", - "@deepseek-ai/dsh-scope": "^0.1.0-rc.8", - "@deepseek-ai/dsh-session": "^0.1.0-rc.8", - "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.8" + "@deepseek-ai/cordis": "^4.0.2", + "@deepseek-ai/dsh-agent": "^0.1.2-rc.1", + "@deepseek-ai/dsh-brand": "^0.1.2-rc.1", + "@deepseek-ai/dsh-invariants": "^0.1.2-rc.1", + "@deepseek-ai/dsh-llm": "^0.1.2-rc.1", + "@deepseek-ai/dsh-scope": "^0.1.2-rc.1", + "@deepseek-ai/dsh-session": "^0.1.2-rc.1", + "@deepseek-ai/dsh-system-prompt": "^0.1.2-rc.1" + } + }, + "node_modules/@deepseek-ai/dsh-util-crypto": { + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-util-crypto/-/dsh-util-crypto-0.1.2-rc.1.tgz", + "integrity": "sha512-gE8FznQ1hkknw9QMrHNSlm3UEyRYuj3MLVvlJ6NyH1kmOsdjnbrVbSs+Pn72k2oIV4Fiyu3Umg0sLznUEcsiXw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@deepseek-ai/cordis": "^4.0.2" + } + }, + "node_modules/@deepseek-ai/dsh-util-values": { + "version": "0.1.2-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-util-values/-/dsh-util-values-0.1.2-rc.1.tgz", + "integrity": "sha512-FQdgoK+KYVR0pBQb9fTsajvGb3gzUxEzJQEYnFOt+Qdwc4nkxJOPwJJdIvXuqvZaQ33DaOoqpnpcr2p5HpLu4g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@deepseek-ai/cordis": "^4.0.2" } }, "node_modules/@deepseek-ai/schemastery": { - "version": "3.18.1", - "resolved": "https://registry.npmjs.org/@deepseek-ai/schemastery/-/schemastery-3.18.1.tgz", - "integrity": "sha512-Qn0FCSwCQnpnj6SB31I6i2sIKgKWnkbJM8O0EU91Gv2UsYVvtZTl6IA0sCwk2e2MZf5S8w5hpq9QkeVvK9qwxg==", + "version": "3.18.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/schemastery/-/schemastery-3.18.2.tgz", + "integrity": "sha512-njDtZsznjYxok7KLLlHOPyuv2efdWVbSflAHgztSfbMsg+CVraEoRe2DjOCgClYv3ZCSm7WXoaUkbB/+RY7tWQ==", "dev": true, "license": "MIT", "dependencies": { - "@deepseek-ai/cosmokit": "^1.8.2", + "@deepseek-ai/cosmokit": "^1.8.3", "@standard-schema/spec": "^1.1.0" } }, diff --git a/typescript/package.json b/typescript/package.json index e5093de9..c64572ae 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -28,13 +28,15 @@ "dsh/cordis.patch.yml", "openclaw.plugin.json", "README.md", - "README_ZH.md" + "README_ZH.md", + "docs", + "figures/dsh" ], "dsh": { "client": { "inject": [ "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-connection", "@deepseek-ai/dsh-client-ui-settings", "@deepseek-ai/dsh-client-ui-settings-plugins", "@deepseek-ai/dsh-client-ui-primitives" @@ -75,13 +77,13 @@ "typebox": "1.3.19" }, "peerDependencies": { - "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/dsh-llm": "^0.1.0-rc.8", - "@deepseek-ai/dsh-settings": "^0.1.0-rc.8", - "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.8", - "@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.8", - "@deepseek-ai/dsh-tools": "^0.1.0-rc.8", - "@deepseek-ai/schemastery": "^3.18.1", + "@deepseek-ai/cordis": "^4.0.2", + "@deepseek-ai/dsh-llm": "^0.1.2-rc.1", + "@deepseek-ai/dsh-settings": "^0.1.2-rc.1", + "@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-rc.1", + "@deepseek-ai/dsh-typert-protocol": "^0.1.2-rc.1", + "@deepseek-ai/dsh-tools": "^0.1.2-rc.1", + "@deepseek-ai/schemastery": "^3.18.2", "openclaw": ">=2026.7.1" }, "peerDependenciesMeta": { @@ -111,13 +113,13 @@ } }, "devDependencies": { - "@deepseek-ai/cordis": "4.0.1", - "@deepseek-ai/dsh-llm": "0.1.0-rc.8", - "@deepseek-ai/dsh-settings": "0.1.0-rc.8", - "@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.8", - "@deepseek-ai/dsh-typert-protocol": "0.1.0-rc.8", - "@deepseek-ai/dsh-tools": "0.1.0-rc.8", - "@deepseek-ai/schemastery": "3.18.1", + "@deepseek-ai/cordis": "4.0.2", + "@deepseek-ai/dsh-llm": "0.1.2-rc.1", + "@deepseek-ai/dsh-settings": "0.1.2-rc.1", + "@deepseek-ai/dsh-client-ui-primitives": "0.1.2-rc.1", + "@deepseek-ai/dsh-typert-protocol": "0.1.2-rc.1", + "@deepseek-ai/dsh-tools": "0.1.2-rc.1", + "@deepseek-ai/schemastery": "3.18.2", "@eslint/js": "9.39.4", "@types/node": "^22.15.0", "@types/react": "~18.3.1", diff --git a/typescript/scripts/pack-clawhub.mjs b/typescript/scripts/pack-clawhub.mjs index d42e8970..415fc063 100644 --- a/typescript/scripts/pack-clawhub.mjs +++ b/typescript/scripts/pack-clawhub.mjs @@ -21,6 +21,14 @@ const temporaryDirectory = await mkdtemp( ); const stagingDirectory = path.join(temporaryDirectory, "package"); +async function stageReadme(source, destination, oldLink, newLink) { + const content = await readFile(path.join(packageDirectory, source), "utf8"); + await writeFile( + path.join(stagingDirectory, destination), + content.replace(oldLink, newLink), + ); +} + try { await mkdir(stagingDirectory, { recursive: true }); await mkdir(destinationDirectory, { recursive: true }); @@ -49,13 +57,17 @@ try { path.join(packageDirectory, "openclaw.plugin.json"), path.join(stagingDirectory, "openclaw.plugin.json"), ), - copyFile( - path.join(packageDirectory, "README_OPENCLAW.md"), - path.join(stagingDirectory, "README.md"), + stageReadme( + "docs/openclaw.md", + "README.md", + "(./openclaw.zh-CN.md)", + "(./README_ZH.md)", ), - copyFile( - path.join(packageDirectory, "README_OPENCLAW_ZH.md"), - path.join(stagingDirectory, "README_ZH.md"), + stageReadme( + "docs/openclaw.zh-CN.md", + "README_ZH.md", + "(./openclaw.md)", + "(./README.md)", ), ]); diff --git a/typescript/scripts/test-package.mjs b/typescript/scripts/test-package.mjs index 3a9e7654..c976241c 100644 --- a/typescript/scripts/test-package.mjs +++ b/typescript/scripts/test-package.mjs @@ -56,6 +56,9 @@ try { sourceManifest.version, )});`, 'assert.match(await readFile(new URL("README_ZH.md", manifestUrl), "utf8"), /TypeScript Agent/);', + 'assert.match(await readFile(new URL("docs/dsh.md", manifestUrl), "utf8"), /Memory context injection/);', + 'assert.match(await readFile(new URL("docs/openclaw.md", manifestUrl), "utf8"), /OpenClaw/);', + 'assert.ok((await readFile(new URL("figures/dsh/reme-status-overview.png", manifestUrl))).length > 0);', ].join("\n"), ); await execFileAsync("node", [consumerEntry], { cwd: temporaryDirectory }); @@ -101,8 +104,10 @@ try { ); assert.match(clawHubReadme, /^# ReMe memory for OpenClaw/m); assert.doesNotMatch(clawHubReadme, /DeepSeek Harness/); + assert.match(clawHubReadme, /\[中文说明\]\(\.\/README_ZH\.md\)/); assert.match(clawHubReadmeZh, /^# OpenClaw 的 ReMe 长期记忆插件/m); assert.doesNotMatch(clawHubReadmeZh, /DeepSeek Harness/); + assert.match(clawHubReadmeZh, /\[English\]\(\.\/README\.md\)/); assert.equal( JSON.parse( await readFile( diff --git a/typescript/src/dsh/client/status-page.tsx b/typescript/src/dsh/client/status-page.tsx index 62958648..8aa70e92 100644 --- a/typescript/src/dsh/client/status-page.tsx +++ b/typescript/src/dsh/client/status-page.tsx @@ -121,6 +121,9 @@ export const statusEn = { documentContent: "Document content", selectFile: "Select a file to preview its contents.", fileLoadFailed: "Could not load this file.", + copyCode: "Copy", + copiedCode: "Copied", + footnotes: "Footnotes", journalDescription: "Daily notes captured from conversations and sources.", knowledgeDescription: "Consolidated knowledge organized for long-term recall.", @@ -229,6 +232,9 @@ export const statusZh: typeof statusEn = { documentContent: "文档内容", selectFile: "选择一个文件查看内容。", fileLoadFailed: "无法读取这个文件。", + copyCode: "复制", + copiedCode: "已复制", + footnotes: "脚注", journalDescription: "从对话和其他来源自动沉淀的每日记录。", knowledgeDescription: "经过整理、适合长期检索和持续积累的个人知识。", }; @@ -929,6 +935,13 @@ function MemoryFiles({ directory: string; t: StatusTranslator; }): JSX.Element { + const markdownLabels = useMemo( + () => ({ + code: { copyLabel: t("copyCode"), copiedLabel: t("copiedCode") }, + footnotes: t("footnotes"), + }), + [t], + ); const [files, setFiles] = useState([]); const [limited, setLimited] = useState(false); const [loading, setLoading] = useState(true); @@ -1084,7 +1097,7 @@ function MemoryFiles({

{t("documentContent")}

- +
diff --git a/typescript/src/dsh/config.ts b/typescript/src/dsh/config.ts index 76f55823..addd077a 100644 --- a/typescript/src/dsh/config.ts +++ b/typescript/src/dsh/config.ts @@ -1,11 +1,10 @@ import z from "@deepseek-ai/schemastery"; -import { settingsNamespace } from "@deepseek-ai/dsh-settings"; import { nextDailyRun, validTimezone } from "../core/scheduling.js"; import type { ReMeConfig, ReMeConfigInput, ReMeSettings } from "./types.js"; /** Durable DSH settings section owned by the ReMe integration. */ -export const REME_SETTINGS_NAMESPACE = settingsNamespace("reme-memory"); +export const REME_SETTINGS_NAMESPACE = "reme-memory"; export const Config = z.object({ endpoint: z.string().description("ReMe HTTP service URL"), diff --git a/typescript/src/dsh/index.ts b/typescript/src/dsh/index.ts index 6a08fb34..a7333ad0 100644 --- a/typescript/src/dsh/index.ts +++ b/typescript/src/dsh/index.ts @@ -1,6 +1,6 @@ import { createUserMessage } from "@deepseek-ai/dsh-llm"; import type { Context } from "@deepseek-ai/cordis"; -import { installSettingsSection } from "@deepseek-ai/dsh-settings"; +import type {} from "@deepseek-ai/dsh-settings"; import { ReMeClient } from "../core/client.js"; import { @@ -22,25 +22,26 @@ export const inject = ["agents", "sessions", "tools"]; export function apply(ctx: Context, input: ReMeConfigInput = {}): void { const base = resolveConfig(input); - let settingsSource: () => ReMeSettings = () => settingsFrom(base); + const defaultSettings = settingsFrom(base); + let settingsSource: () => ReMeSettings = () => defaultSettings; const current = () => mergeSettings(base, settingsSource()); const client = new ReMeClient(current); const runtime = new ReMeRuntime(client, current, ctx.logger); - installSettingsSection( - ctx, - REME_SETTINGS_NAMESPACE, - SettingsConfig, - settingsFrom(base), - { - setSource: (source) => { - settingsSource = source; + ctx.inject(["settings"], (settingsCtx) => { + settingsCtx.settings.installSection( + ctx, + REME_SETTINGS_NAMESPACE, + SettingsConfig, + defaultSettings, + { + setSource(source) { + settingsSource = source; + }, + onChange: () => runtime.reconfigure(), + validate: validateSettings, }, - onChange: () => { - runtime.reconfigure(); - }, - validate: validateSettings, - }, - ); + ); + }); ctx.provide("remeMemory", runtime); void ctx.plugin(ReMeStatusGateway); ctx.effect( diff --git a/typescript/tests/bundle.test.mjs b/typescript/tests/bundle.test.mjs index 0b9a9387..7f1216e3 100644 --- a/typescript/tests/bundle.test.mjs +++ b/typescript/tests/bundle.test.mjs @@ -32,15 +32,21 @@ test("declares DSH and OpenClaw entries in one installable package", async () => "@deepseek-ai/dsh-client-ui-primitives", ), ); + assert.ok( + manifest.dsh.client.inject.includes("@deepseek-ai/dsh-client-connection"), + ); + assert.ok( + !manifest.dsh.client.inject.includes("@deepseek-ai/dsh-client-runtime"), + ); assert.equal(manifest.dsh.bundle.patch, "./dsh/cordis.patch.yml"); assert.deepEqual(manifest.openclaw.extensions, ["./dist/openclaw/index.js"]); assert.equal( manifest.peerDependencies["@deepseek-ai/dsh-llm"], - "^0.1.0-rc.8", + "^0.1.2-rc.1", ); assert.equal( manifest.peerDependencies["@deepseek-ai/dsh-tools"], - "^0.1.0-rc.8", + "^0.1.2-rc.1", ); assert.match(patch, /remeMemory: true/); assert.match(patch, /@agentscope-ai\/reme\/dsh/); diff --git a/typescript/tests/dsh-index.test.mjs b/typescript/tests/dsh-index.test.mjs index 9e49f885..29eec1cd 100644 --- a/typescript/tests/dsh-index.test.mjs +++ b/typescript/tests/dsh-index.test.mjs @@ -137,23 +137,18 @@ test("registers a ReMe settings namespace and reads changed values for new sessi }, inject(names, callback) { if (!names.includes("settings")) return; - callback({ + const settingsCtx = { settings: { - register(ns, _schema, options) { - section = options.base; + installSection(owner, ns, _schema, base, hooks) { + assert.equal(owner, ctx); + section = base; assert.equal(String(ns), "reme-memory"); - return { - get: () => section, - watch(listener) { - notify = listener; - }, - }; + hooks.setSource(() => section); + notify = hooks.onChange; }, }, - effect(execute) { - return execute(); - }, - }); + }; + callback(settingsCtx); }, }; apply(ctx, {