From fbbc5d271538ebb32ef01906998bb3d5b4befe0a Mon Sep 17 00:00:00 2001 From: "jinli.yl" Date: Thu, 27 Aug 2026 22:00:07 +0800 Subject: [PATCH] refactor(config): remove daily_cookbook and streamline plugin configs - Delete the entire daily_cookbook.yaml standalone application config - Remove qwenpaw dependencies verification and related CI workflow steps - Simplify release workflows by removing qwenpaw verification and enforcing reme-ai >=0.4.1.9 - Update plugin start commands and examples to use 'default' or 'demo' configs instead of daily_cookbook - Adjust imports and tests related to daily_cookbook removal and injected_job_kwargs enhancements - Refactor agent wrapper to support injected_job_kwargs for job parameter injection in auto-fin and daily-paper - Improve daily_paper digest prompt to include configured daily directory and correct historical search constraints - Update dependency versions in pyproject.toml files to require reme-ai >=0.4.1.9 and remove qwenpaw optional dependencies - Clean up unused environment variables and obsolete test cases related to daily_cookbook and verification steps --- .github/workflows/_build-python-packages.yml | 21 - .github/workflows/release-auto-fin.yml | 5 +- .github/workflows/release-daily-paper.yml | 5 +- .github/workflows/release-python.yml | 9 - docs/en/plugin_management.md | 4 +- docs/zh/plugin_management.md | 4 +- plugins/auto-fin/README.md | 4 +- plugins/auto-fin/README_ZH.md | 4 +- plugins/auto-fin/pyproject.toml | 2 +- plugins/auto-fin/src/reme_auto_fin/base.py | 3 + plugins/auto-fin/src/reme_auto_fin/merge.py | 9 +- plugins/auto-fin/tests/test_auto_fin.py | 11 +- plugins/daily_paper/pyproject.toml | 2 +- .../src/reme_daily_paper/digest.py | 14 +- .../src/reme_daily_paper/digest.yaml | 6 +- plugins/daily_paper/tests/test_daily_paper.py | 27 ++ pyproject.toml | 5 - reme/config/daily_cookbook.yaml | 416 ------------------ tests/unit/test_config_parser.py | 8 - tests/unit/test_dingtalk_wait.py | 52 +-- tests/unit/test_injected_job_kwargs.py | 27 +- tests/unit/test_package_versions.py | 13 +- tests/unit/test_plugin_cli.py | 2 +- 23 files changed, 114 insertions(+), 539 deletions(-) delete mode 100644 reme/config/daily_cookbook.yaml diff --git a/.github/workflows/_build-python-packages.yml b/.github/workflows/_build-python-packages.yml index 2fb3552e..2802bfcf 100644 --- a/.github/workflows/_build-python-packages.yml +++ b/.github/workflows/_build-python-packages.yml @@ -13,11 +13,6 @@ on: required: false default: false type: boolean - verify_qwenpaw_dependencies: - description: Verify that independently published qwenpaw plugins are installable - required: false - default: true - type: boolean permissions: contents: read @@ -84,22 +79,6 @@ jobs: assert (static_dir() / "index.html").is_file() PY - # qwenpaw composes independently released plugins. Bootstrap releases may - # skip this check to publish the reme-ai version required by those plugins. - - name: Verify released qwenpaw dependencies - if: inputs.expected_version != '' && inputs.verify_qwenpaw_dependencies - run: | - REME_WHEEL="$(pwd)/$(ls dist/reme/reme_ai-[0-9]*.whl)" - python -m venv "${RUNNER_TEMP}/reme-qwenpaw-package-smoke" - "${RUNNER_TEMP}/reme-qwenpaw-package-smoke/bin/python" -m pip install "${REME_WHEEL}[qwenpaw]" - cd "${RUNNER_TEMP}" - "${RUNNER_TEMP}/reme-qwenpaw-package-smoke/bin/python" - <<'PY' - from importlib.metadata import distribution - - assert distribution("reme-auto-fin") - assert distribution("reme-daily-paper") - PY - - name: Upload ReMe distributions if: inputs.upload_artifacts uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 diff --git a/.github/workflows/release-auto-fin.yml b/.github/workflows/release-auto-fin.yml index 90af3b17..d45164b6 100644 --- a/.github/workflows/release-auto-fin.yml +++ b/.github/workflows/release-auto-fin.yml @@ -75,6 +75,8 @@ jobs: reme_requirement = Requirement(requirements[0]) if reme_requirement.name != "reme-ai" or reme_requirement.extras: raise SystemExit(f"Expected a base reme-ai dependency, found {requirements[0]!r}") + if Version("0.4.1.8") in reme_requirement.specifier or Version("0.4.1.9") not in reme_requirement.specifier: + raise SystemExit(f"Expected reme-ai>=0.4.1.9, found {requirements[0]!r}") with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as output: print(f"reme_requirement={reme_requirement}", file=output) print(f"Publishing {project['name']} {actual}") @@ -104,7 +106,8 @@ jobs: python -m zipfile -l "${AUTO_FIN_WHEEL}" | grep 'dist-info/licenses/LICENSE' python -m tarfile -l "${AUTO_FIN_SDIST}" | grep '/LICENSE' python -m venv "${RUNNER_TEMP}/reme-auto-fin-smoke" - "${RUNNER_TEMP}/reme-auto-fin-smoke/bin/python" -m pip install "${AUTO_FIN_WHEEL}" + "${RUNNER_TEMP}/reme-auto-fin-smoke/bin/python" -m pip install \ + "agentscope[model-ollama]==2.0.7" "${AUTO_FIN_WHEEL}" cd "${RUNNER_TEMP}" "${RUNNER_TEMP}/reme-auto-fin-smoke/bin/python" - <<'PY' from importlib.metadata import distribution diff --git a/.github/workflows/release-daily-paper.yml b/.github/workflows/release-daily-paper.yml index acc5cc81..12b06767 100644 --- a/.github/workflows/release-daily-paper.yml +++ b/.github/workflows/release-daily-paper.yml @@ -72,6 +72,8 @@ jobs: reme_requirements = [requirement for requirement in requirements if requirement.name == "reme-ai"] if len(reme_requirements) != 1 or reme_requirements[0].extras: raise SystemExit(f"Expected one base reme-ai dependency, found {reme_requirements!r}") + if Version("0.4.1.8") in reme_requirements[0].specifier or Version("0.4.1.9") not in reme_requirements[0].specifier: + raise SystemExit(f"Expected reme-ai>=0.4.1.9, found {reme_requirements!r}") if sum(requirement.name == "pypdf" for requirement in requirements) != 1: raise SystemExit("Expected exactly one pypdf dependency") with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as output: @@ -105,7 +107,8 @@ jobs: python -m zipfile -l "${DAILY_PAPER_WHEEL}" | grep 'dist-info/licenses/LICENSE' python -m tarfile -l "${DAILY_PAPER_SDIST}" | grep '/LICENSE' python -m venv "${RUNNER_TEMP}/reme-daily-paper-smoke" - "${RUNNER_TEMP}/reme-daily-paper-smoke/bin/python" -m pip install "${DAILY_PAPER_WHEEL}" + "${RUNNER_TEMP}/reme-daily-paper-smoke/bin/python" -m pip install \ + "agentscope[model-ollama]==2.0.7" "${DAILY_PAPER_WHEEL}" cd "${RUNNER_TEMP}" "${RUNNER_TEMP}/reme-daily-paper-smoke/bin/python" - <<'PY' from importlib.metadata import distribution diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index 361c861d..0d05410a 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -1,8 +1,5 @@ name: Release / Python packages -# reme-ai[qwenpaw] is normally verified before publication. For a bootstrap -# release where the plugins require this new reme-ai version, disable that -# verification, publish reme-ai first, and then publish the plugins. # Configure a PyPI Trusted Publisher for this repository, workflow, and its # pypi environment before running the manual release. @@ -13,11 +10,6 @@ on: description: Release version required: true type: string - verify_qwenpaw_dependencies: - description: Verify already-published Auto Fin and Daily Paper packages - required: true - default: true - type: boolean permissions: contents: read @@ -33,7 +25,6 @@ jobs: with: expected_version: ${{ inputs.version }} upload_artifacts: true - verify_qwenpaw_dependencies: ${{ inputs.verify_qwenpaw_dependencies }} publish-reme: needs: build diff --git a/docs/en/plugin_management.md b/docs/en/plugin_management.md index bbc71900..46a729be 100644 --- a/docs/en/plugin_management.md +++ b/docs/en/plugin_management.md @@ -64,7 +64,7 @@ reme plugins list --json To compare installed plugins with one application config: ```bash -reme plugins list --config daily_cookbook +reme plugins list --config default ``` The optional `ENABLED` column reflects only the `plugins` list resolved from that config. A command-line override used @@ -176,7 +176,7 @@ When the application uses an MCP service, service-enabled plugin Jobs appear as To add the plugin to another application config, select it explicitly: ```bash -reme start config=daily_cookbook plugins='["auto-fin"]' +reme start config=demo plugins='["auto-fin"]' ``` ## Uninstall a plugin diff --git a/docs/zh/plugin_management.md b/docs/zh/plugin_management.md index aa174b61..0ab75aa8 100644 --- a/docs/zh/plugin_management.md +++ b/docs/zh/plugin_management.md @@ -61,7 +61,7 @@ reme plugins list --json 对照某个应用配置查看启用状态: ```bash -reme plugins list --config daily_cookbook +reme plugins list --config default ``` 可选的 `ENABLED` 列只反映该配置解析出的 `plugins` 列表。其他运行中进程使用的 CLI override 不是全局启用状态。 @@ -170,7 +170,7 @@ curl -s http://127.0.0.1:2333/auto_fin \ 如果需要将插件叠加到其他应用配置,则显式选择该配置: ```bash -reme start config=daily_cookbook plugins='["auto-fin"]' +reme start config=demo plugins='["auto-fin"]' ``` ## 卸载插件 diff --git a/plugins/auto-fin/README.md b/plugins/auto-fin/README.md index 7c1519d6..804e0fdb 100644 --- a/plugins/auto-fin/README.md +++ b/plugins/auto-fin/README.md @@ -62,7 +62,7 @@ reme start plugins='["auto-fin"]' \ To add Auto Fin to another application instead, select that config explicitly, for example: ```bash -reme start config=daily_cookbook plugins='["auto-fin"]' +reme start config=demo plugins='["auto-fin"]' ``` ## Pipeline @@ -109,7 +109,7 @@ refreshes the daily index. No JSONL, intermediate Markdown, or structured Agent | `request_interval` | `10` | Minimum delay in seconds after every CLS request attempt; may be zero | | `max_retries` | `3` | Maximum attempts for each CLS page request; must be at least one | -The three plugin cron Jobs start with the application and run daily at 09:30, 11:30, and 18:00 in `Asia/Shanghai`. +The plugin cron Job starts with the application and runs daily at 18:00 in the application timezone. ## Output diff --git a/plugins/auto-fin/README_ZH.md b/plugins/auto-fin/README_ZH.md index ab4d9a96..a100804a 100644 --- a/plugins/auto-fin/README_ZH.md +++ b/plugins/auto-fin/README_ZH.md @@ -57,7 +57,7 @@ reme start plugins='["auto-fin"]' \ 如果需要将 Auto Fin 叠加到其他应用,则显式选择相应配置,例如: ```bash -reme start config=daily_cookbook plugins='["auto-fin"]' +reme start config=demo plugins='["auto-fin"]' ``` ## 流程 @@ -100,7 +100,7 @@ workspace 的 Markdown 目标。不存在、绝对路径、越界、带反斜杠 | `request_interval` | `10` | 每次财联社请求尝试后的最小等待秒数,可设为 0 | | `max_retries` | `3` | 每页财联社请求的最大尝试次数,至少为 1 | -插件的三个 cron Job 随应用启动,并按 `Asia/Shanghai` 时区在每天 09:30、11:30 和 18:00 运行。 +插件的 cron Job 随应用启动,并按应用配置的时区在每天 18:00 运行。 ## 产物 diff --git a/plugins/auto-fin/pyproject.toml b/plugins/auto-fin/pyproject.toml index 674b1396..f09d4ea0 100644 --- a/plugins/auto-fin/pyproject.toml +++ b/plugins/auto-fin/pyproject.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" license-files = ["LICENSE"] requires-python = ">=3.11" dependencies = [ - "reme-ai", + "reme-ai>=0.4.1.9", ] [project.entry-points."reme.plugins"] diff --git a/plugins/auto-fin/src/reme_auto_fin/base.py b/plugins/auto-fin/src/reme_auto_fin/base.py index 31d3ea95..37388b00 100644 --- a/plugins/auto-fin/src/reme_auto_fin/base.py +++ b/plugins/auto-fin/src/reme_auto_fin/base.py @@ -76,6 +76,7 @@ class AutoFinStep(BaseStep): prompt_name: str, model: type[BaseModel], job_tools: list[str] | None = None, + injected_job_kwargs: dict[str, Any] | None = None, **values: str, ) -> BaseModel: if self.agent_wrapper is None: @@ -89,6 +90,8 @@ class AutoFinStep(BaseStep): kwargs: dict[str, Any] = {"output_schema": model} if job_tools: kwargs["job_tools"] = job_tools + if injected_job_kwargs: + kwargs["injected_job_kwargs"] = injected_job_kwargs result = await self.agent_wrapper.reply(prompt, **kwargs) if not isinstance(result, dict) or result.get("structured_output") is None: raise ValueError(f"Auto Fin Agent returned no structured output: {self._preview(result)}") diff --git a/plugins/auto-fin/src/reme_auto_fin/merge.py b/plugins/auto-fin/src/reme_auto_fin/merge.py index e7beae67..2b5b8d1f 100644 --- a/plugins/auto-fin/src/reme_auto_fin/merge.py +++ b/plugins/auto-fin/src/reme_auto_fin/merge.py @@ -4,7 +4,7 @@ from __future__ import annotations import json import re -from datetime import date +from datetime import date, timedelta from pathlib import Path from types import SimpleNamespace @@ -107,10 +107,17 @@ class AutoFinMergeStep(AutoFinStep): if self.context.get("auto_fin_skipped"): return self.context.response run_date = date.fromisoformat(str(self._required("auto_fin_date"))) + historical_search = { + "limit": 5, + "min_score": 0.0, + "start_date": None, + "end_date": (run_date - timedelta(days=1)).isoformat(), + } output = await self._reply( "merge_user", AutoFinReportOutput, job_tools=list(self.kwargs.get("job_tools") or []), + injected_job_kwargs=historical_search, decision_at=str(self._required("auto_fin_decision_at")), window_start=str(self._required("auto_fin_window_start")), topics=json.dumps(self._required("auto_fin_topics"), ensure_ascii=False), diff --git a/plugins/auto-fin/tests/test_auto_fin.py b/plugins/auto-fin/tests/test_auto_fin.py index ef3af8f3..2815dd91 100644 --- a/plugins/auto-fin/tests/test_auto_fin.py +++ b/plugins/auto-fin/tests/test_auto_fin.py @@ -202,7 +202,16 @@ async def test_merge_writes_only_final_report_and_validates_historical_links(tmp assert "end_date" not in prompt assert "调用 `search`" in prompt assert "调用 `read`" in prompt - assert kwargs == {"output_schema": AutoFinReportOutput, "job_tools": ["search", "read"]} + assert kwargs == { + "output_schema": AutoFinReportOutput, + "job_tools": ["search", "read"], + "injected_job_kwargs": { + "limit": 5, + "min_score": 0.0, + "start_date": None, + "end_date": "2026-08-09", + }, + } report = (tmp_path / "daily" / "2026-08-10" / "auto_fin.md").read_text(encoding="utf-8") assert "[[daily/2026-08-01/auto_fin.md|历史黄金观察]]" in report assert "](daily/2026-08-01/auto_fin.md)" not in report diff --git a/plugins/daily_paper/pyproject.toml b/plugins/daily_paper/pyproject.toml index 564ec760..cbdd51a2 100644 --- a/plugins/daily_paper/pyproject.toml +++ b/plugins/daily_paper/pyproject.toml @@ -8,7 +8,7 @@ license-files = ["LICENSE"] requires-python = ">=3.11" dependencies = [ "pypdf>=5.0.0", - "reme-ai", + "reme-ai>=0.4.1.9", ] [project.entry-points."reme.plugins"] diff --git a/plugins/daily_paper/src/reme_daily_paper/digest.py b/plugins/daily_paper/src/reme_daily_paper/digest.py index c8d028ed..121ee8f6 100644 --- a/plugins/daily_paper/src/reme_daily_paper/digest.py +++ b/plugins/daily_paper/src/reme_daily_paper/digest.py @@ -80,14 +80,23 @@ class DailyPaperDigestStep(DailyPaperStep): documents = [{"title": item.title, "desc": item.desc, "body": item.body} for item in analyses] wikilinks = [f"[[{item.note_path}]]" for item in analyses] + run_day = dt.date.fromisoformat(self._run_day()) + daily_dir = str(self.config_value("daily_dir")).strip("/") self.logger.info(f"[{self.name}] agent start notes={len(analyses)}") result = await self.agent_wrapper.reply( self.prompt_format( "digest_user", documents=json.dumps(documents, ensure_ascii=False, indent=2), + daily_dir=daily_dir, ), output_schema=DailyPaperMarkdownOutput, job_tools=list(self.kwargs.get("job_tools") or []), + injected_job_kwargs={ + "limit": 20, + "min_score": 0.0, + "start_date": None, + "end_date": (run_day - dt.timedelta(days=1)).isoformat(), + }, ) self.logger.info(f"[{self.name}] agent done notes={len(analyses)}") output = structured_output(result, DailyPaperMarkdownOutput) @@ -95,8 +104,7 @@ class DailyPaperDigestStep(DailyPaperStep): if not output.desc.strip() or not body: raise ValueError("Agent returned an empty daily paper brief") - day = self._run_day() - daily_dir = str(self.config_value("daily_dir")).strip("/") + day = run_day.isoformat() title = normalize_chinese_title(output.title, f"每日论文简报-{day}") existing_rel = str(self._state("existing_digest_path") or "").strip() existing_path = self.workspace_path / existing_rel if existing_rel else None @@ -109,7 +117,7 @@ class DailyPaperDigestStep(DailyPaperStep): existing=existing_path, ) digest_rel = digest_path.relative_to(self.workspace_path).as_posix() - body = self._validate_historical_wikilinks(body, dt.date.fromisoformat(day), digest_path) + body = self._validate_historical_wikilinks(body, run_day, digest_path) body += "\n\n## 详细论文\n\n" + "\n".join(f"- {link}" for link in wikilinks) selected_ids = [item.arxiv_id for item in analyses] await write_markdown( diff --git a/plugins/daily_paper/src/reme_daily_paper/digest.yaml b/plugins/daily_paper/src/reme_daily_paper/digest.yaml index 9b847c8d..bb8c1a20 100644 --- a/plugins/daily_paper/src/reme_daily_paper/digest.yaml +++ b/plugins/daily_paper/src/reme_daily_paper/digest.yaml @@ -4,12 +4,12 @@ digest_user: | 内容只能依据输入文档,不得补充文档中没有提供的事实。 保留技术准确性,同时解释三篇论文为什么值得关注,以及它们之间有什么联系。 - 在写作前,先调用 `search` 检索以前的文章:围绕三篇论文的核心问题、方法、关键词和同义表达组织查询, - 使用 limit=20。主题跨度较大时可以多次检索。只把 `daily/` 下日期早于今天、 + 在写作前,先调用 `search` 检索以前的文章:围绕三篇论文的核心问题、方法、关键词和同义表达组织查询。 + 主题跨度较大时可以多次检索。只把 `{daily_dir}/` 下日期早于今天、 且与本期内容确实相似或互补的 Markdown 文章作为候选;必要时调用 `read` 核验全文,不要仅凭标题判断。 将确认相关的旧文章以 Wikilink 自然织入正文,并用句子说明关联(延续、对比、补充或方法相似); 链接必须采用带 `.md` 的完整 workspace-relative 路径,例如 - `[[daily/2026-07-01/旧文章.md|此前的相关解读]]`。不要输出裸链接、独立关系字段,也不要虚构搜索未命中的路径。 + `[[{daily_dir}/2026-07-01/旧文章.md|此前的相关解读]]`。不要输出裸链接、独立关系字段,也不要虚构搜索未命中的路径。 旧文章只用于判断关联和建立链接,不得用来补充本期事实。如果没有真正相关的旧文章,不要强行添加; 当日三篇详细解读的链接会由系统统一附在文末。 diff --git a/plugins/daily_paper/tests/test_daily_paper.py b/plugins/daily_paper/tests/test_daily_paper.py index 9590e9a9..29ed9cb6 100644 --- a/plugins/daily_paper/tests/test_daily_paper.py +++ b/plugins/daily_paper/tests/test_daily_paper.py @@ -627,6 +627,26 @@ def test_daily_paper_cron_hf_mirror_defaults_enabled_with_environment_override(m assert _plugin_config()["jobs"]["daily_paper_cron"]["use_hf_mirror"] is False +def test_digest_prompt_uses_configured_daily_directory(tmp_path: Path): + """Use the host application's daily directory in historical-link guidance.""" + step = DailyPaperDigestStep( + app_context=ApplicationContext( + workspace_dir=str(tmp_path), + daily_dir="memory", + ), + ) + + prompt = step.prompt_format( + "digest_user", + documents="[]", + daily_dir=str(step.config_value("daily_dir")).strip("/"), + ) + + assert "`memory/`" in prompt + assert "[[memory/2026-07-01/旧文章.md" in prompt + assert "[[daily/2026-07-01/" not in prompt + + def test_paper_pick_list_uses_an_object_root_for_tool_output(): """AgentScope function arguments require an object-root JSON schema.""" schema = PaperPickList.model_json_schema() @@ -887,6 +907,12 @@ async def test_pipeline_filters_strict_yesterday_and_writes_outputs( assert cc_wrapper.calls[-1]["kwargs"] == { "output_schema": DailyPaperMarkdownOutput, "job_tools": ["search", "read"], + "injected_job_kwargs": { + "limit": 20, + "min_score": 0.0, + "start_date": None, + "end_date": "2026-07-20", + }, } assert [call["kwargs"]["output_schema"] for call in cc_wrapper.calls] == [ PaperPickList, @@ -908,6 +934,7 @@ async def test_pipeline_filters_strict_yesterday_and_writes_outputs( assert "长期记忆" not in digest_prompt assert "先调用 `search` 检索以前的文章" in digest_prompt assert "end_date" not in digest_prompt + assert "limit=" not in digest_prompt assert "Wikilink" in digest_prompt rerun = RuntimeContext(date="2026-07-21") diff --git a/pyproject.toml b/pyproject.toml index 472edca3..e1affe28 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,11 +62,6 @@ core = [ "polars>=1.43.0", "reme_studio", ] -qwenpaw = [ - "reme-ai", - "reme-auto-fin>=0.1.2", - "reme-daily-paper>=0.1.2", -] dev = [ "packaging>=24.2", "pre-commit>=4.6.1", diff --git a/reme/config/daily_cookbook.yaml b/reme/config/daily_cookbook.yaml deleted file mode 100644 index 20409880..00000000 --- a/reme/config/daily_cookbook.yaml +++ /dev/null @@ -1,416 +0,0 @@ -app_name: ReMe Daily Cookbook -workspace_dir: ${DAILY_PAPER_WORKSPACE_DIR:-reme_workspace} -timezone: Asia/Shanghai -language: zh - -# This is a standalone application config. It intentionally does not inherit -# default.yaml and listens on a separate port so it can run beside ReMe. -service: - backend: http - host: ${DAILY_PAPER_HOST:-127.0.0.1} - port: ${DAILY_PAPER_PORT:-8001} - -jobs: - index_update_loop: - backend: background - watch_dirs: [daily_dir, digest_dir] - watch_suffixes: [md, jsonl] - steps: - - backend: init_changes_step - monitor_type: file_store - monitor_name: default - dispatch_steps: [update_index_step] - - backend: watch_changes_step - dispatch_steps: - - backend: update_index_step - persist: false - - auto_dream: - backend: base - description: "Auto-dream: consolidate recent daily notes into digest memory and interest topics." - parameters: - type: object - properties: - date: - type: string - description: "YYYY-MM-DD to scan; defaults to today in the configured timezone." - default: "" - hint: - type: string - description: "Optional guidance for extraction and integration." - default: "" - scan_days: - type: integer - description: "Number of recent daily directories to scan." - default: 2 - max_units: - type: integer - description: "Maximum number of extracted memory units." - default: 5 - topic_count: - type: integer - description: "Maximum number of interest topics to write." - default: 3 - topic_diversity_days: - type: integer - description: "Previous interest-topic days used for de-duplication." - 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: - backend: base - description: "Auto-memory: record conversation facts into a daily note." - parameters: - type: object - properties: - messages: - type: array - description: "Conversation messages." - items: - type: object - session_id: - type: string - description: "Source conversation session identifier." - default: "" - memory_hint: - type: string - description: "Optional memory-writing guidance." - date: - type: string - description: "YYYY-MM-DD daily-note date; empty infers it from messages or current time." - default: "" - required: [messages] - steps: - - backend: auto_memory_step - - reindex: - backend: base - description: "Wipe the derived search store and rebuild it from memory files." - watch_dirs: [daily_dir, digest_dir] - watch_suffixes: [md, jsonl] - parameters: - type: object - properties: {} - steps: - - backend: clear_store_step - - backend: init_changes_step - monitor_type: file_store - monitor_name: default - dispatch_steps: [update_index_step] - - memory_search: - backend: base - description: "Long-term memory retrieval via hybrid workspace search (vector + BM25, RRF-fused)." - parameters: - type: object - properties: - query: - type: string - description: "Search query." - limit: - type: integer - description: "Maximum number of results." - default: 5 - min_score: - type: number - description: "Minimum fused score." - default: 0.0 - start_date: - type: string - description: "Optional inclusive start date (YYYY-MM-DD)." - end_date: - type: string - description: "Optional inclusive end date (YYYY-MM-DD)." - required: [query] - steps: - - backend: search_step - vector_weight: 0.7 - candidate_multiplier: 5.0 - expand_links: true - max_links_per_direction: 10 - - node_search: - backend: base - description: "Recall digest nodes for auto-dream de-duplication and linking." - parameters: - type: object - properties: - query: - type: string - description: "Candidate memory-node name and description." - limit: - type: integer - description: "Maximum number of digest nodes." - default: 20 - required: [query] - steps: - - backend: node_search_step - vector_weight: 0.7 - candidate_multiplier: 5.0 - - daily_list: - backend: base - description: "List notes under one day." - parameters: - type: object - properties: - date: - type: string - description: "YYYY-MM-DD; empty means today." - default: "" - steps: - - backend: daily_list_step - - frontmatter_read: - backend: base - description: "Read a file's frontmatter." - parameters: - type: object - properties: - path: - type: string - description: "Workspace-relative path." - required: [path] - steps: - - backend: frontmatter_read_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 or rename a workspace file and retarget inbound wikilinks." - parameters: - type: object - properties: - src_path: - type: string - description: "Workspace-relative source path." - dst_path: - type: string - description: "Workspace-relative destination path." - 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 markdown path." - 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: "Create or overwrite a markdown file with frontmatter." - parameters: - type: object - properties: - path: - type: string - description: "Workspace-relative markdown path." - name: - type: string - description: "Frontmatter name." - description: - type: string - description: "Frontmatter description." - content: - type: string - description: "Markdown body." - metadata: - type: object - description: "Optional extra frontmatter fields." - required: [path, name, description, content] - steps: - - backend: write_step - - daily_write: - backend: base - description: "Write a daily markdown note linked to its source conversation." - parameters: - type: object - properties: - name: - type: string - description: "Filename stem and frontmatter name." - description: - type: string - description: "Frontmatter description." - session_id: - type: string - description: "Source conversation session identifier." - content: - type: string - description: "Markdown body." - date: - type: string - description: "YYYY-MM-DD; empty means today." - default: "" - metadata: - type: object - description: "Optional extra frontmatter fields." - required: [name, description, session_id, content] - steps: - - backend: daily_write_step - - edit: - backend: base - description: "Find and replace text in a markdown file." - parameters: - type: object - properties: - path: - type: string - description: "Workspace-relative path." - old: - type: string - description: "Text to replace." - new: - type: string - description: "Replacement text." - default: "" - required: [path, old, new] - steps: - - backend: edit_step - - dingtalk_wait: - backend: background - supervisor: true - close_timeout: 10 - steps: - - backend: dingtalk_wait_step - app_key: ${DINGTALK_APP_KEY:-} - app_secret: ${DINGTALK_APP_SECRET:-} - robot_code: ${DINGTALK_ROBOT_CODE:-} - worker_count: 4 - builtin_tools: [bash] - job_tools: - - memory_search - - read - - write - - edit - - daily_list - - daily_write - - frontmatter_read - - frontmatter_update - -components: - tokenizer: - default: - backend: regex - - as_llm: - default: - backend: openai - model: ${LLM_MODEL_NAME:-qwen3.7-plus} - stream: true - context_size: 200000 - max_retries: 3 - 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 - builtin_tools: false - -# as_embedding: -# default: -# backend: openai -# model: ${EMBEDDING_MODEL_NAME:-text-embedding-v4} -# dimensions: 1024 -# max_retries: 0 -# credential: -# api_key: ${EMBEDDING_API_KEY:-} -# base_url: ${EMBEDDING_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1} -# parameters: {} -# -# embedding_store: -# default: -# backend: local -# as_embedding: default -# max_retries: 3 -# quota_retry_delay: 60.0 - - file_graph: - default: - backend: local - - file_catalog: - 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: [] - jsonl: - backend: jsonl - supported_extensions: [jsonl] - max_lines_per_chunk: 1 - - keyword_index: - default: - backend: bm25 - tokenizer: default - - file_store: - default: - backend: local - store_name: local -# embedding_store: default - embedding_store: "" - keyword_index: default - file_graph: default diff --git a/tests/unit/test_config_parser.py b/tests/unit/test_config_parser.py index b518db20..16ba54bf 100644 --- a/tests/unit/test_config_parser.py +++ b/tests/unit/test_config_parser.py @@ -123,14 +123,6 @@ def test_default_config_keeps_frontmatter_chunk_metadata_opt_in(): ) in (None, []) -def test_daily_cookbook_chunks_jsonl_one_line_at_a_time(): - """Daily cookbook keeps JSONL records as individually addressable chunks.""" - cfg = _load_config("daily_cookbook.yaml") - - jsonl = cfg["components"]["file_chunker"]["jsonl"] - assert jsonl["max_lines_per_chunk"] == 1 - - def test_parse_args_rejects_non_key_value_extra_argument(): """Extra CLI arguments must use key=value syntax.""" with pytest.raises(ValueError, match="expected key=value"): diff --git a/tests/unit/test_dingtalk_wait.py b/tests/unit/test_dingtalk_wait.py index 5bbefade..73a44b3c 100644 --- a/tests/unit/test_dingtalk_wait.py +++ b/tests/unit/test_dingtalk_wait.py @@ -9,10 +9,8 @@ from unittest.mock import MagicMock import pytest -from reme.components import ApplicationContext, R +from reme.components import ApplicationContext from reme.components.agent_wrapper.base_agent_wrapper import BaseAgentWrapper -from reme.config.config_parser import _load_config -from reme.enumeration import ComponentEnum from reme.steps.cookbook.dingtalk.wait import DingTalkWaitStep, _session_key @@ -203,54 +201,6 @@ async def test_final_reply_injects_only_configured_tools(tmp_path): ] -def test_daily_cookbook_registers_one_step_background_wait_job(monkeypatch): - for name in ("DINGTALK_APP_KEY", "DINGTALK_APP_SECRET", "DINGTALK_ROBOT_CODE"): - monkeypatch.delenv(name, raising=False) - config = _load_config("daily_cookbook") - job = config["jobs"]["dingtalk_wait"] - assert job["backend"] == "background" - assert job["steps"] == [ - { - "backend": "dingtalk_wait_step", - "app_key": "", - "app_secret": "", - "robot_code": "", - "worker_count": 4, - "builtin_tools": ["bash"], - "job_tools": [ - "memory_search", - "read", - "write", - "edit", - "daily_list", - "daily_write", - "frontmatter_read", - "frontmatter_update", - ], - }, - ] - assert config["components"]["agent_wrapper"] == { - "default": { - "backend": "agentscope", - "as_llm": "default", - "builtin_tools": False, - }, - } - assert R.get(ComponentEnum.STEP, "dingtalk_wait_step") is DingTalkWaitStep - - -def test_daily_cookbook_passes_dingtalk_environment_to_step(monkeypatch): - monkeypatch.setenv("DINGTALK_APP_KEY", "app-key") - monkeypatch.setenv("DINGTALK_APP_SECRET", "app-secret") - monkeypatch.setenv("DINGTALK_ROBOT_CODE", "robot-code") - step = _load_config("daily_cookbook")["jobs"]["dingtalk_wait"]["steps"][0] - assert (step["app_key"], step["app_secret"], step["robot_code"]) == ( - "app-key", - "app-secret", - "robot-code", - ) - - @pytest.mark.asyncio async def test_stream_client_closes_when_background_stop_is_set(monkeypatch): websocket = _WebSocket() diff --git a/tests/unit/test_injected_job_kwargs.py b/tests/unit/test_injected_job_kwargs.py index 5f4185fe..847b2757 100644 --- a/tests/unit/test_injected_job_kwargs.py +++ b/tests/unit/test_injected_job_kwargs.py @@ -65,6 +65,31 @@ def test_strip_injected_parameters_hides_keys_from_schema(): assert "date" in job.parameters["properties"] +def test_search_injection_exposes_only_query(): + parameters = { + "type": "object", + "properties": { + "query": {"type": "string"}, + "limit": {"type": "integer"}, + "min_score": {"type": "number"}, + "start_date": {"type": "string"}, + "end_date": {"type": "string"}, + }, + "required": ["query"], + } + injected = { + "limit": 20, + "min_score": 0.0, + "start_date": None, + "end_date": "2026-07-20", + } + + stripped = BaseAgentWrapper._strip_injected_parameters(parameters, injected) + + assert stripped["properties"] == {"query": {"type": "string"}} + assert stripped["required"] == ["query"] + + # -- AgentScope wrapper ----------------------------------------------------------- @@ -139,7 +164,7 @@ class _RecordingWrapper(BaseAgentWrapper): super().__init__(**kwargs) self.calls: list[dict] = [] - async def reply(self, inputs, **kwargs) -> dict: + async def reply(self, _inputs, **kwargs) -> dict: self.calls.append(kwargs) return {"session_id": "s-1", "last_message": {}, "result": "ok"} diff --git a/tests/unit/test_package_versions.py b/tests/unit/test_package_versions.py index 373aff64..8d68f65e 100644 --- a/tests/unit/test_package_versions.py +++ b/tests/unit/test_package_versions.py @@ -7,6 +7,7 @@ import tomllib from types import ModuleType from packaging.requirements import Requirement +from packaging.version import Version import pytest REPOSITORY = Path(__file__).resolve().parents[2] @@ -45,11 +46,7 @@ def test_studio_packages_have_independent_identity() -> None: assert main_config["project"]["optional-dependencies"]["web"] == ["reme_studio"] assert main_config["project"]["optional-dependencies"]["core"].count("reme-ai[as]") == 1 assert main_config["project"]["optional-dependencies"]["core"].count("reme_studio") == 1 - assert main_config["project"]["optional-dependencies"]["qwenpaw"] == [ - "reme-ai", - "reme-auto-fin>=0.1.2", - "reme-daily-paper>=0.1.2", - ] + assert "qwenpaw" not in main_config["project"]["optional-dependencies"] assert auto_fin_config["project"]["version"] == "0.1.2" assert daily_paper_config["project"]["version"] == "0.1.2" assert main_config["tool"]["setuptools"]["packages"]["find"]["include"] == ["reme", "reme.*"] @@ -161,7 +158,8 @@ def test_auto_fin_requires_reme_base() -> None: assert len(reme_requirements) == 1 assert not reme_requirements[0].extras - assert not reme_requirements[0].specifier + assert Version("0.4.1.8") not in reme_requirements[0].specifier + assert Version("0.4.1.9") in reme_requirements[0].specifier def test_daily_paper_license_matches_repository() -> None: @@ -178,7 +176,8 @@ def test_daily_paper_declares_runtime_dependencies() -> None: by_name = {requirement.name: requirement for requirement in requirements} assert not by_name["reme-ai"].extras - assert not by_name["reme-ai"].specifier + assert Version("0.4.1.8") not in by_name["reme-ai"].specifier + assert Version("0.4.1.9") in by_name["reme-ai"].specifier assert "pypdf" in by_name diff --git a/tests/unit/test_plugin_cli.py b/tests/unit/test_plugin_cli.py index ff06868b..d1cfa925 100644 --- a/tests/unit/test_plugin_cli.py +++ b/tests/unit/test_plugin_cli.py @@ -76,7 +76,7 @@ def test_list_plugins_marks_configured_plugins(monkeypatch, tmp_path, capsys): ) monkeypatch.setattr(plugin_cli_module, "_enabled_plugins", lambda _config: {"auto-fin"}) - assert plugin_cli_module.plugin_cli(["list", "--config", "daily_cookbook"]) == 0 + assert plugin_cli_module.plugin_cli(["list", "--config", "default"]) == 0 output = capsys.readouterr().out assert "ENABLED" in output