mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-11-04 07:30:21 +00:00
fix(qoder): address plugin review feedback
This commit is contained in:
parent
8158fa80e3
commit
75ab722aa2
5 changed files with 280 additions and 57 deletions
|
|
@ -9,53 +9,116 @@ directory packages ReMe automatic recall, completed-turn recording, MCP memory t
|
|||
## Requirements
|
||||
|
||||
- Qoder CLI 1.1.41 or newer with plugin support.
|
||||
- Python 3.11 or newer as `python3` in the Qoder environment, with FastMCP 3.1 or newer.
|
||||
- A running ReMe service exposing the MCP endpoint and the HTTP jobs `search` and `auto_memory`.
|
||||
- Python 3.11 or newer available as `python3` in the environment Qoder uses for plugin hooks and
|
||||
MCP servers.
|
||||
- FastMCP 3.1 or newer in that same Python environment. The `reme-ai[core]` install includes it.
|
||||
- A separately running ReMe service exposing the MCP endpoint and the HTTP jobs `search` and
|
||||
`auto_memory`.
|
||||
|
||||
The default split development setup uses:
|
||||
## 1. Prepare ReMe
|
||||
|
||||
- MCP: `http://127.0.0.1:2333/mcp`
|
||||
- HTTP Job API: `http://127.0.0.1:2334`
|
||||
Install ReMe and its Python dependencies from this repository. Use the Python interpreter that
|
||||
Qoder can access as `python3`.
|
||||
|
||||
## Install
|
||||
macOS or Linux:
|
||||
|
||||
From this checkout:
|
||||
```bash
|
||||
cd /path/to/ReMe
|
||||
python3 -m pip install -e ".[core]"
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
qodercli plugins marketplace add E:\path\to\ReMe\integrations\qoder
|
||||
cd E:\path\to\ReMe
|
||||
python -m pip install -e ".[core]"
|
||||
```
|
||||
|
||||
Automatic memory generation requires model credentials. Configure `LLM_API_KEY` and
|
||||
`LLM_BASE_URL` as described in the [Quick Start](../../README.md#environment-variables).
|
||||
|
||||
Start ReMe in a separate terminal and leave it running:
|
||||
|
||||
```bash
|
||||
reme start
|
||||
```
|
||||
|
||||
The default HTTP service serves both the Job API and streamable HTTP MCP on
|
||||
`http://127.0.0.1:2333`. The default Qoder configuration therefore uses:
|
||||
|
||||
- MCP: `http://127.0.0.1:2333/mcp`
|
||||
- HTTP Job API: derived from `mcp_url` as `http://127.0.0.1:2333`
|
||||
|
||||
If the deployment intentionally uses separate ports, set `api_url` explicitly.
|
||||
|
||||
## 2. Install the plugin
|
||||
|
||||
The plugin is distributed from the repository-local marketplace. Keep the checkout available when
|
||||
installing or updating it.
|
||||
|
||||
macOS or Linux:
|
||||
|
||||
```bash
|
||||
cd /path/to/ReMe
|
||||
reme_root="$(pwd)"
|
||||
qodercli plugins validate "$reme_root/integrations/qoder/reme" --strict
|
||||
qodercli plugins marketplace add "$reme_root/integrations/qoder"
|
||||
qodercli plugins install reme@reme-marketplace --scope user
|
||||
```
|
||||
|
||||
Direct local installation is also supported:
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
qodercli plugins install E:\path\to\ReMe\integrations\qoder\reme --scope user
|
||||
cd E:\path\to\ReMe
|
||||
$remeRoot = (Resolve-Path .).Path
|
||||
qodercli plugins validate "$remeRoot\integrations\qoder\reme" --strict
|
||||
qodercli plugins marketplace add "$remeRoot\integrations\qoder"
|
||||
qodercli plugins install reme@reme-marketplace --scope user
|
||||
```
|
||||
|
||||
Validate the package before installing:
|
||||
Direct installation from the plugin directory is also supported:
|
||||
|
||||
```powershell
|
||||
qodercli plugins validate E:\path\to\ReMe\integrations\qoder\reme --strict
|
||||
```bash
|
||||
qodercli plugins install /path/to/ReMe/integrations/qoder/reme --scope user
|
||||
```
|
||||
|
||||
## Configure
|
||||
## 3. Configure
|
||||
|
||||
The plugin reads `%USERPROFILE%\.qoder\reme\config.json`, or
|
||||
`${QODER_CONFIG_DIR}/reme/config.json` when `QODER_CONFIG_DIR` is set:
|
||||
The plugin reads `~/.qoder/reme/config.json`, or `${QODER_CONFIG_DIR}/reme/config.json` when
|
||||
`QODER_CONFIG_DIR` is set.
|
||||
|
||||
macOS or Linux:
|
||||
|
||||
```bash
|
||||
mkdir -p "$HOME/.qoder/reme"
|
||||
cp integrations/qoder/reme/config.example.json "$HOME/.qoder/reme/config.json"
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force "$HOME\.qoder\reme"
|
||||
Copy-Item integrations\qoder\reme\config.example.json "$HOME\.qoder\reme\config.json"
|
||||
Copy-Item .\integrations\qoder\reme\config.example.json "$HOME\.qoder\reme\config.json"
|
||||
```
|
||||
|
||||
`mcp_url` controls the MCP tool connection. `api_url` controls the HTTP job API used by lifecycle
|
||||
hooks. The two must not be conflated when the ReMe deployment uses separate ports.
|
||||
The relevant defaults are:
|
||||
|
||||
Set `memory_interval` to `1` for immediate per-turn writes. The remaining keys and limits match the
|
||||
Claude Code integration:
|
||||
```json
|
||||
{
|
||||
"mcp_url": "http://127.0.0.1:2333/mcp",
|
||||
"api_url": "",
|
||||
"memory_interval": 1
|
||||
}
|
||||
```
|
||||
|
||||
An empty `api_url` is derived from `mcp_url` when that URL ends in `/mcp`. Set `api_url` only for a
|
||||
split-port or reverse-proxy deployment. The shipped example sets `memory_interval` to `1` for
|
||||
immediate per-turn writes; the code default is `5`.
|
||||
|
||||
| Option | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `mcp_url` | `http://127.0.0.1:2333/mcp` | ReMe MCP endpoint |
|
||||
| `api_url` | `""` | Optional HTTP Job API override; derived from `mcp_url` when empty |
|
||||
| `auto_recall` | `true` | Search before a user prompt |
|
||||
| `auto_memory` | `true` | Capture and submit completed turns |
|
||||
| `recall_limit` | `5` | Search result limit |
|
||||
|
|
@ -67,6 +130,9 @@ Claude Code integration:
|
|||
| `context_max_chars` | `8000` | Maximum recalled context |
|
||||
| `timezone` | `Asia/Shanghai` | Daily batch timezone |
|
||||
|
||||
Restart Qoder after installation, or run `/plugins reload`. Reconnect MCP after changing
|
||||
`mcp_url`; hook configuration changes apply on the next invocation.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
`UserPromptSubmit` performs bounded recall and injects results as untrusted historical evidence.
|
||||
|
|
@ -77,7 +143,8 @@ endpoint and protected by per-session locks. Stable message IDs make repeated St
|
|||
|
||||
Qoder transcript records use Claude-style `user` and `assistant` JSONL rows. Tool calls, tool
|
||||
results, reasoning blocks, metadata messages, subagents, and injected `<reme-context>` are excluded.
|
||||
The hook accepts complete final JSONL rows while Qoder finishes materializing the transcript.
|
||||
The hook waits for the current final JSONL row instead of treating an older identical reply as the
|
||||
new turn.
|
||||
|
||||
## Migration from manual hooks
|
||||
|
||||
|
|
@ -88,16 +155,25 @@ memory-gate hooks should remain untouched.
|
|||
|
||||
## Verify
|
||||
|
||||
```powershell
|
||||
qodercli plugins list
|
||||
qodercli mcp list
|
||||
First verify that Qoder discovered the plugin resources:
|
||||
|
||||
```bash
|
||||
qodercli plugins list --json
|
||||
```
|
||||
|
||||
Open a new Qoder conversation through CodexHost or directly. Success requires:
|
||||
The `reme` entry should be enabled and report one skill, one `reme` MCP server, and five hook
|
||||
registrations. `qodercli mcp list` does not report servers bundled by plugins, so it is not a valid
|
||||
connection test.
|
||||
|
||||
1. a `memory_saved` row in `%USERPROFILE%\.qoder\reme\hooks.log`;
|
||||
2. the corresponding queue `.done` receipt;
|
||||
3. one new `session/dialog/qoder-*.jsonl` file with a user and final-assistant row.
|
||||
Open a fresh Qoder session and ask it to call the ReMe `health_check` and `version` MCP tools. Then
|
||||
finish one memory-worthy turn with `memory_interval` set to `1` and verify all three signals:
|
||||
|
||||
1. a content-free `memory_saved` row in `~/.qoder/reme/hooks.log`;
|
||||
2. the corresponding queue `.done` receipt under `~/.qoder/reme/queue/`;
|
||||
3. a new `session/dialog/qoder-*.jsonl` file containing the user and final-assistant rows.
|
||||
|
||||
Open a second Qoder session and ask for the fact from the first session. Confirm that recall returns
|
||||
it before considering the installation complete.
|
||||
|
||||
The service-owned `auto_dream` consolidation job remains enabled according to the ReMe service
|
||||
configuration; the plugin does not start another scheduler.
|
||||
|
|
|
|||
|
|
@ -3,47 +3,139 @@
|
|||
[English](./README.md)
|
||||
|
||||
Qoder 支持把 Skills、MCP、Hooks 和配置打包成插件。本目录提供 ReMe 的 Qoder 整包集成:
|
||||
自动召回、每轮完成的对话采集、MCP 记忆工具,以及 `reme-memory` Skill。
|
||||
自动召回、完成轮次记录、MCP 记忆工具,以及 `reme-memory` Skill。
|
||||
|
||||
## 安装
|
||||
## 环境要求
|
||||
|
||||
- Qoder CLI 1.1.41 或更新版本,并支持插件。
|
||||
- Qoder 执行 Hook 和 MCP Server 的环境中可以调用 `python3`,且 Python 版本为 3.11 或更新版本。
|
||||
- 同一个 Python 环境中安装 FastMCP 3.1 或更新版本;`reme-ai[core]` 会一并安装。
|
||||
- 单独运行一个 ReMe 服务,提供 MCP endpoint 以及 `search`、`auto_memory` HTTP Job。
|
||||
|
||||
## 1. 准备 ReMe
|
||||
|
||||
在本仓库中安装 ReMe 和 Python 依赖。请使用 Qoder 能通过 `python3` 调用的同一个解释器。
|
||||
|
||||
macOS 或 Linux:
|
||||
|
||||
```bash
|
||||
cd /path/to/ReMe
|
||||
python3 -m pip install -e ".[core]"
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
qodercli plugins marketplace add E:\path\to\ReMe\integrations\qoder
|
||||
cd E:\path\to\ReMe
|
||||
python -m pip install -e ".[core]"
|
||||
```
|
||||
|
||||
自动生成记忆需要模型凭据。请按照 [快速开始](../../README_ZH.md#环境变量配置) 配置
|
||||
`LLM_API_KEY` 和 `LLM_BASE_URL`。
|
||||
|
||||
在另一个终端启动 ReMe,并保持服务运行:
|
||||
|
||||
```bash
|
||||
reme start
|
||||
```
|
||||
|
||||
默认 HTTP 服务在同一端口提供 Job API 和 streamable HTTP MCP:
|
||||
|
||||
- MCP:`http://127.0.0.1:2333/mcp`
|
||||
- HTTP Job API:从 `mcp_url` 推导为 `http://127.0.0.1:2333`
|
||||
|
||||
如果确实使用拆分端口部署,才需要显式设置 `api_url`。
|
||||
|
||||
## 2. 安装插件
|
||||
|
||||
插件从仓库内的本地 marketplace 发布。安装或升级时需要使用这个 ReMe checkout。
|
||||
|
||||
macOS 或 Linux:
|
||||
|
||||
```bash
|
||||
cd /path/to/ReMe
|
||||
reme_root="$(pwd)"
|
||||
qodercli plugins validate "$reme_root/integrations/qoder/reme" --strict
|
||||
qodercli plugins marketplace add "$reme_root/integrations/qoder"
|
||||
qodercli plugins install reme@reme-marketplace --scope user
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
cd E:\path\to\ReMe
|
||||
$remeRoot = (Resolve-Path .).Path
|
||||
qodercli plugins validate "$remeRoot\integrations\qoder\reme" --strict
|
||||
qodercli plugins marketplace add "$remeRoot\integrations\qoder"
|
||||
qodercli plugins install reme@reme-marketplace --scope user
|
||||
```
|
||||
|
||||
也可以直接从插件目录安装:
|
||||
|
||||
```powershell
|
||||
qodercli plugins install E:\path\to\ReMe\integrations\qoder\reme --scope user
|
||||
```bash
|
||||
qodercli plugins install /path/to/ReMe/integrations/qoder/reme --scope user
|
||||
```
|
||||
|
||||
安装前可运行:
|
||||
## 3. 配置
|
||||
|
||||
```powershell
|
||||
qodercli plugins validate E:\path\to\ReMe\integrations\qoder\reme --strict
|
||||
用户配置位于 `~/.qoder/reme/config.json`,也可以通过 `QODER_CONFIG_DIR` 改配置根。
|
||||
|
||||
macOS 或 Linux:
|
||||
|
||||
```bash
|
||||
mkdir -p "$HOME/.qoder/reme"
|
||||
cp integrations/qoder/reme/config.example.json "$HOME/.qoder/reme/config.json"
|
||||
```
|
||||
|
||||
## 配置
|
||||
Windows PowerShell:
|
||||
|
||||
用户配置位于 `%USERPROFILE%\.qoder\reme\config.json`,也可以通过 `QODER_CONFIG_DIR` 改配置根。
|
||||
默认拆分服务为:
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force "$HOME\.qoder\reme"
|
||||
Copy-Item .\integrations\qoder\reme\config.example.json "$HOME\.qoder\reme\config.json"
|
||||
```
|
||||
|
||||
- MCP:`http://127.0.0.1:2333/mcp`
|
||||
- Hook HTTP Job API:`http://127.0.0.1:2334`
|
||||
关键默认配置如下:
|
||||
|
||||
把 `memory_interval` 设为 `1` 可在每轮结束后立即写入。`auto_recall` 控制提醒前召回,
|
||||
`auto_memory` 控制完成轮次采集;超时与上下文上限和 Claude Code 集成一致。
|
||||
```json
|
||||
{
|
||||
"mcp_url": "http://127.0.0.1:2333/mcp",
|
||||
"api_url": "",
|
||||
"memory_interval": 1
|
||||
}
|
||||
```
|
||||
|
||||
当 `mcp_url` 以 `/mcp` 结尾时,空的 `api_url` 会自动推导为同一服务地址。只有拆分端口或
|
||||
反向代理部署才需要设置 `api_url`。仓库中的示例把 `memory_interval` 设为 `1`,用于每轮立即写入;
|
||||
代码默认值为 `5`。
|
||||
|
||||
| 配置项 | 默认值 | 含义 |
|
||||
| --- | --- | --- |
|
||||
| `mcp_url` | `http://127.0.0.1:2333/mcp` | ReMe MCP 地址 |
|
||||
| `api_url` | `""` | 可选的 HTTP Job API 地址;为空时从 `mcp_url` 推导 |
|
||||
| `auto_recall` | `true` | 用户提问前自动召回 |
|
||||
| `auto_memory` | `true` | 采集并提交完成轮次 |
|
||||
| `recall_limit` | `5` | 召回结果上限 |
|
||||
| `recall_min_score` | `0` | 最低召回分数 |
|
||||
| `recall_timeout` | `5` | 前台召回超时,最多 10 秒 |
|
||||
| `request_timeout` | `600` | 记忆写入超时,最多 600 秒 |
|
||||
| `memory_interval` | `5` | 多少轮完成后批量写入 |
|
||||
| `shutdown_timeout` | `2` | 会话结束时尽力排空,最多 2 秒 |
|
||||
| `context_max_chars` | `8000` | 注入的召回上下文上限 |
|
||||
| `timezone` | `Asia/Shanghai` | 按天分批使用的时区 |
|
||||
|
||||
安装后重启 Qoder,或执行 `/plugins reload`。修改 `mcp_url` 后需要重新连接 MCP;Hook 配置会在
|
||||
下次调用时生效。
|
||||
|
||||
## 工作方式
|
||||
|
||||
`UserPromptSubmit` 同步召回并注入不受信任的历史证据;`Stop` 先同步捕获本轮 user/最终
|
||||
assistant 文本到本地队列,再由并行异步 Hook 调用 `POST {api_url}/auto_memory`。
|
||||
`SessionStart` 重试残留队列,`SessionEnd` 做短时排空。
|
||||
`UserPromptSubmit` 执行有上限的召回,并作为不受信任的历史证据注入。`Stop` 先同步捕获本轮
|
||||
user/最终 assistant 文本到本地队列,再由并行异步 Hook 调用 `POST {api_url}/auto_memory`。
|
||||
`SessionStart` 重试残留队列,`SessionEnd` 做短时排空。队列按服务地址隔离,并带进程锁和
|
||||
`.done` 回执;稳定的消息 ID 保证重复 Stop 不会重复写入。
|
||||
|
||||
队列按服务地址隔离,带进程锁和 `.done` 回执;工具输出、推理、子代理、注入的
|
||||
`<reme-context>` 都会被排除。Qoder 使用 Claude 风格 JSONL transcript,Hook 支持等到最后一行
|
||||
完整落盘后再捕获。
|
||||
Qoder 使用 Claude 风格 JSONL transcript。工具输出、推理、元数据消息、子代理和注入的
|
||||
`<reme-context>` 都会被排除。当连续两轮最终回复相同时,插件会等待当前轮的新 transcript
|
||||
行,而不会把上一轮的相同回复当成新轮次。
|
||||
|
||||
## 从手工 Hook 迁移
|
||||
|
||||
|
|
@ -53,11 +145,22 @@ Qoder、Orca、记忆库门禁等其他 Hook 不要删除。
|
|||
|
||||
## 验收
|
||||
|
||||
```powershell
|
||||
qodercli plugins list
|
||||
qodercli mcp list
|
||||
首先确认 Qoder 发现了插件资源:
|
||||
|
||||
```bash
|
||||
qodercli plugins list --json
|
||||
```
|
||||
|
||||
新开一个 Qoder 会话后,应同时看到 `%USERPROFILE%\.qoder\reme\hooks.log` 中的
|
||||
`memory_saved`、队列 `.done` 回执,以及新的 `session/dialog/qoder-*.jsonl` user/assistant 两行。
|
||||
`reme` 条目应当已启用,并显示 1 个 Skill、1 个 `reme` MCP Server 和 5 个 Hook 注册项。
|
||||
`qodercli mcp list` 不会列出插件内置的 MCP Server,因此不能作为连接是否正常的判断依据。
|
||||
|
||||
新开一个 Qoder 会话,让 Agent 实际调用 ReMe 的 `health_check` 和 `version` MCP 工具。然后把
|
||||
`memory_interval` 设为 `1`,完成一轮值得记忆的对话,并同时确认:
|
||||
|
||||
1. `~/.qoder/reme/hooks.log` 中出现不包含正文的 `memory_saved`;
|
||||
2. `~/.qoder/reme/queue/` 下出现对应的 `.done` 回执;
|
||||
3. 新的 `session/dialog/qoder-*.jsonl` 中包含该轮 user 和最终 assistant 两行。
|
||||
|
||||
最后新开第二个 Qoder 会话,询问第一轮中的事实,确认自动召回成功后再认为安装完成。
|
||||
|
||||
`auto_dream` 仍由 ReMe 服务自身调度,插件不会额外启动调度器。
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"mcp_url": "http://127.0.0.1:2333/mcp",
|
||||
"api_url": "http://127.0.0.1:2334",
|
||||
"api_url": "",
|
||||
"auto_recall": true,
|
||||
"auto_memory": true,
|
||||
"recall_limit": 5,
|
||||
|
|
|
|||
|
|
@ -265,9 +265,15 @@ def capture(config: dict, payload: dict) -> Path | None:
|
|||
if root is None:
|
||||
return None
|
||||
deadline = time.monotonic() + CAPTURE_SETTLE_SECONDS
|
||||
messages = []
|
||||
while True:
|
||||
messages = completed_turn(payload, root.name)
|
||||
if len(messages) == 2 or time.monotonic() >= deadline:
|
||||
if len(messages) == 2:
|
||||
# An identical reply may still resolve to the previous turn while Qoder appends this turn.
|
||||
key = digest(messages[-1]["id"])
|
||||
if not (root / f"{key}.done").exists() and not (root / f"{key}.json").exists():
|
||||
break
|
||||
if time.monotonic() >= deadline:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
if len(messages) != 2:
|
||||
|
|
|
|||
|
|
@ -139,6 +139,44 @@ def test_claude_synchronous_capture_preserves_identical_replies_before_delayed_d
|
|||
assert len(list(adapter.queue_root(adapter.load_config()).rglob("*.done"))) == 2
|
||||
|
||||
|
||||
def test_qoder_capture_waits_for_delayed_identical_reply(adapter, tmp_path, monkeypatch):
|
||||
require_host(adapter, "qoder")
|
||||
monkeypatch.setattr(adapter, "CAPTURE_SETTLE_SECONDS", 0.5)
|
||||
config = adapter.load_config()
|
||||
path = tmp_path / "transcript.jsonl"
|
||||
|
||||
first = transcript(adapter, path, [("first", "Saved.")])
|
||||
root = adapter.capture(config, first)
|
||||
first_pending = next(root.glob("*.json"))
|
||||
adapter.write_json(first_pending.with_suffix(".done"), {})
|
||||
first_pending.unlink()
|
||||
|
||||
second = transcript(adapter, path, [("first", "Saved."), ("second", "Saved.")])
|
||||
lines = path.read_text(encoding="utf-8").splitlines(keepends=True)
|
||||
delayed_reply = lines[-1]
|
||||
path.write_text("".join(lines[:-1]), encoding="utf-8")
|
||||
|
||||
def append_delayed_reply():
|
||||
with path.open("a", encoding="utf-8") as handle:
|
||||
handle.write(delayed_reply)
|
||||
|
||||
captured = []
|
||||
worker = threading.Thread(target=lambda: captured.append(adapter.capture(config, second)))
|
||||
timer = threading.Timer(0.05, append_delayed_reply)
|
||||
timer.start()
|
||||
worker.start()
|
||||
worker.join(3)
|
||||
timer.join(3)
|
||||
|
||||
assert not worker.is_alive()
|
||||
assert not timer.is_alive()
|
||||
assert captured == [root]
|
||||
pending = list(root.glob("*.json"))
|
||||
assert len(pending) == 1
|
||||
messages = json.loads(pending[0].read_text(encoding="utf-8"))["messages"]
|
||||
assert [message["content"][0]["text"] for message in messages] == ["second", "Saved."]
|
||||
|
||||
|
||||
def test_claude_delivery_waits_for_parallel_synchronous_capture(adapter, tmp_path, monkeypatch):
|
||||
require_structured_transcript_host(adapter)
|
||||
write_config(adapter, {"memory_interval": 1})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue