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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"base": {
"repos/jiuwenswarm/components/common-core/rules.md": "---\ntitle: \"jiuwenswarm/common 审查规则:配置读写事务、跨仓契约与持久化键稳定性\"\ncreated: 2026-09-30\nupdated: 2026-09-30\ntype: rule\ntags: [jiuwenswarm]\nsources: []\n---\n\n# jiuwenswarm/common 审查规则:配置读写事务、跨仓契约与持久化键稳定性\n\n## JIUWENSW-I1 — `jiuwenswarm/common/config.py::update_config` 的 mutator 不能再次进入写锁,返回值决定是否写盘\n\n- `_CONFIG_WRITE_LOCK`(`threading.Lock`)和 portalocker 文件锁都不可重入。如果 mutator 里调用了任何经过 `update_config` 或 `config_write_lock` 的函数(如 `update_ttse_enabled_in_config`、`save_models_candidate`、`update_permissions_*`),同一线程会一直阻塞,到 `lock_timeout` 后抛出 `TimeoutError`,写入失败。\n- mutator 的返回值才是要 dump 的对象。返回 `None` 表示不写盘(`delete_*` 没命中时就靠它跳过)。新写的 mutator 如果漏了 `return data`,修改会被悄悄丢掉。\n- mutator 内抛出异常会中止写入。校验(`raise_if_invalid`、重复 id 检查)必须放在 mutator 里面或调用之前,不能放到 `update_config` 返回之后。\n- 写盘后要读出来展示的数据,应在 `update_config` 返回后另发一次调用,不要在 mutator 里读。\n\n<!-- kb:rule status=active since=init-f0a69728c96b -->\n\n## JIUWENSW-I4 — 新增的 config.yaml 写入口必须走 `update_config` 加 `dump_yaml_round_trip`,不要复制裸读改写的写法\n\n- AgentServer 和 Gateway 两个进程会同时写同一个 config.yaml。`update_channel_in_config`、`update_browser_in_config`、`set_auto_memory_enabled` 这类裸 `load_yaml_round_trip` 到 `dump_yaml_round_trip` 的写法不持锁,会覆盖另一个进程的修改。新代码不要照抄这种写法;PR 改到这些旧函数时,优先迁移到 `update_config`。\n- 不要使用 `set_config`(直接 `open(..., \"w\")` 后 `yaml.safe_dump`),也不要自己开文件写。这种写法不是原子的,读者可能读到写了一半的文件,还会丢掉注释和引号风格。\n- `dump_yaml_round_trip` 的临时文件必须和目标文件放在同一目录(`os.replace` 不能跨文件系统),异常路径要删掉 tmp 文件。不要去掉 `_atomic_replace` 对 Windows `PermissionError` 的重试。\n- `_CONFIG_YAML_PATH`、`_load_yaml_round_trip`、`_dump_yaml_round_trip` 是下游模块在导入的兼容别名,不能删除或改名。\n\n<!-- kb:rule status=active since=init-f0a69728c96b -->\n\n## JIUWENSW-I13 — 读取开关时注意字符串布尔值;evolution 开关只有一个来源\n\n- 通过 `${VAR:-false}` 注入的值是字符串,而 `bool(\"false\")` 为 True。可能来自环境变量替换的新开关应使用 `coerce_config_bool`。`is_subagent_runtime_enabled` 里的 `bool(...)` 就有这个问题,不要照抄。\n- `_get_evolution_config` 有意只读 `react.evolution`,不读顶层的 `evolution`,也不读环境变量覆盖,以免残留的部署变量改变运行中 agent 的能力。`get_skill_evolution_enabled` 和 `get_evolution_auto_save_enabled` 用 `is True` 严格判断。PR 不能把旧路径或 env 回退加回来。\n\n<!-- kb:rule status=active since=init-f0a69728c96b -->\n\n## JIUWENSW-I17 — `jiuwenswarm/common/client/` 下的抽象接口是跨仓库契约,改签名必须和 Gateway 副本同步\n\n- `AgentServerClient` 和 `ThirdAgent` 在 Gateway 仓库里各有一份副本,两边靠 E2A 线协议对齐。修改方法名、keyword-only 参数或返回类型时,必须同步 Gateway 副本和所有实现(WebSocket、AgentOS Router 等)。\n- 新增 `@abstractmethod` 会让所有没实现它的子类无法实例化。可选能力应提供默认实现(参考 `ThirdAgent.normalize_agent_type`)。\n- `send_request` 的 `timeout=None` 表示使用客户端默认值。实现方必须采用调用方传入的更大值(例如 cron 的 `timeout_seconds`),不能再被内层默认值提前截断。\n- `UnsupportedThirdAgent` 的返回形状(`ok` 为 False,带 `error`,`code` 为 UNSUPPORTED)是调用方依赖的约定。`normalize_agent_type` 只对内置的 jiuwenswarm 不区分大小写,registry 里的名称保留原大小写。\n\n<!-- kb:rule status=active since=init-f0a69728c96b -->\n"
},
"evidence": [
{
"kind": "upstream_text",
"source_reference": "openJiuwen-ai/jiuwenswarm@f0a69728c96b:jiuwenswarm/common/chat_final.py:L3-L47",
"text": "\"\"\"chat.final 落地协议字段。\n\n前端优先读 final_mode,避免用字符串 includes 启发式折叠整轮:\n- patch_segment:覆写当前/匹配分段(工具打断后的默认路径)\n- replace_turn:用 final 替换本轮全部助手气泡(需显式声明)\n- append:追加新气泡\n\"\"\"\nfrom __future__ import annotations\n\nfrom typing import Any, Mapping, MutableMapping\n\nFINAL_MODE_PATCH_SEGMENT = \"patch_segment\"\nFINAL_MODE_REPLACE_TURN = \"replace_turn\"\nFINAL_MODE_APPEND = \"append\"\n\n\ndef annotate_chat_final(\n payload: Mapping[str, Any] | None,\n *,\n final_mode: str = FINAL_MODE_PATCH_SEGMENT,\n) -> dict[str, Any]:\n \"\"\"确保 chat.final payload 带 final_mode;已有显式值时不覆盖。\"\"\"\n out: dict[str, Any] = dict(payload or {})\n if out.get(\"event_type\") and str(out.get(\"event_type\")).strip() != \"chat.final\":\n return out\n existing = out.get(\"final_mode\")\n if isinstance(existing, str) and existing.strip():\n return out\n out[\"final_mode\"] = final_mode\n out.setdefault(\"event_type\", \"chat.final\")\n return out\n\n\ndef ensure_final_mode_inplace(\n payload: MutableMapping[str, Any],\n *,\n final_mode: str = FINAL_MODE_PATCH_SEGMENT,\n) -> None:\n \"\"\"就地写入 final_mode(仅当缺失时)。\"\"\"\n if str(payload.get(\"event_type\") or \"\").strip() not in (\"\", \"chat.final\"):\n return\n existing = payload.get(\"final_mode\")\n if isinstance(existing, str) and existing.strip():\n return\n payload[\"final_mode\"] = final_mode"
}
],
"expected": "reject",
"head": {
"repos/jiuwenswarm/components/common-core/rules.md": "---\ntitle: \"jiuwenswarm/common 审查规则:配置读写事务、跨仓契约与持久化键稳定性\"\ncreated: 2026-09-30\nupdated: 2026-09-30\ntype: rule\ntags: [jiuwenswarm]\nsources: []\n---\n\n# jiuwenswarm/common 审查规则:配置读写事务、跨仓契约与持久化键稳定性\n\n## JIUWENSW-I1 — `jiuwenswarm/common/config.py::update_config` 的 mutator 不能再次进入写锁,返回值决定是否写盘\n\n- `_CONFIG_WRITE_LOCK`(`threading.Lock`)和 portalocker 文件锁都不可重入。如果 mutator 里调用了任何经过 `update_config` 或 `config_write_lock` 的函数(如 `update_ttse_enabled_in_config`、`save_models_candidate`、`update_permissions_*`),同一线程会一直阻塞,到 `lock_timeout` 后抛出 `TimeoutError`,写入失败。\n- mutator 的返回值才是要 dump 的对象。返回 `None` 表示不写盘(`delete_*` 没命中时就靠它跳过)。新写的 mutator 如果漏了 `return data`,修改会被悄悄丢掉。\n- mutator 内抛出异常会中止写入。校验(`raise_if_invalid`、重复 id 检查)必须放在 mutator 里面或调用之前,不能放到 `update_config` 返回之后。\n- 写盘后要读出来展示的数据,应在 `update_config` 返回后另发一次调用,不要在 mutator 里读。\n\n<!-- kb:rule status=active since=init-f0a69728c96b -->\n\n## JIUWENSW-I4 — 新增的 config.yaml 写入口必须走 `update_config` 加 `dump_yaml_round_trip`,不要复制裸读改写的写法\n\n- AgentServer 和 Gateway 两个进程会同时写同一个 config.yaml。`update_channel_in_config`、`update_browser_in_config`、`set_auto_memory_enabled` 这类裸 `load_yaml_round_trip` 到 `dump_yaml_round_trip` 的写法不持锁,会覆盖另一个进程的修改。新代码不要照抄这种写法;PR 改到这些旧函数时,优先迁移到 `update_config`。\n- 不要使用 `set_config`(直接 `open(..., \"w\")` 后 `yaml.safe_dump`),也不要自己开文件写。这种写法不是原子的,读者可能读到写了一半的文件,还会丢掉注释和引号风格。\n- `dump_yaml_round_trip` 的临时文件必须和目标文件放在同一目录(`os.replace` 不能跨文件系统),异常路径要删掉 tmp 文件。不要去掉 `_atomic_replace` 对 Windows `PermissionError` 的重试。\n- `_CONFIG_YAML_PATH`、`_load_yaml_round_trip`、`_dump_yaml_round_trip` 是下游模块在导入的兼容别名,不能删除或改名。\n\n<!-- kb:rule status=active since=init-f0a69728c96b -->\n\n## JIUWENSW-I13 — 读取开关时注意字符串布尔值;evolution 开关只有一个来源\n\n- 通过 `${VAR:-false}` 注入的值是字符串,而 `bool(\"false\")` 为 True。可能来自环境变量替换的新开关应使用 `coerce_config_bool`。`is_subagent_runtime_enabled` 里的 `bool(...)` 就有这个问题,不要照抄。\n- `_get_evolution_config` 有意只读 `react.evolution`,不读顶层的 `evolution`,也不读环境变量覆盖,以免残留的部署变量改变运行中 agent 的能力。`get_skill_evolution_enabled` 和 `get_evolution_auto_save_enabled` 用 `is True` 严格判断。PR 不能把旧路径或 env 回退加回来。\n\n<!-- kb:rule status=active since=init-f0a69728c96b -->\n\n## JIUWENSW-I17 — `jiuwenswarm/common/client/` 下的抽象接口是跨仓库契约,改签名必须和 Gateway 副本同步\n\n- `AgentServerClient` 和 `ThirdAgent` 在 Gateway 仓库里各有一份副本,两边靠 E2A 线协议对齐。修改方法名、keyword-only 参数或返回类型时,必须同步 Gateway 副本和所有实现(WebSocket、AgentOS Router 等)。\n- 新增 `@abstractmethod` 会让所有没实现它的子类无法实例化。可选能力应提供默认实现(参考 `ThirdAgent.normalize_agent_type`)。\n- `send_request` 的 `timeout=None` 表示使用客户端默认值。实现方必须采用调用方传入的更大值(例如 cron 的 `timeout_seconds`),不能再被内层默认值提前截断。\n- `UnsupportedThirdAgent` 的返回形状(`ok` 为 False,带 `error`,`code` 为 UNSUPPORTED)是调用方依赖的约定。`normalize_agent_type` 只对内置的 jiuwenswarm 不区分大小写,registry 里的名称保留原大小写。\n\n<!-- kb:rule status=active since=init-f0a69728c96b -->\n\n## JIUWENSW-I15 — `jiuwenswarm/common/chat_final.py` 的 `final_mode` 是和前端约定的线协议\n\n- 前端会直接比对 `FINAL_MODE_PATCH_SEGMENT`/`FINAL_MODE_REPLACE_TURN`/`FINAL_MODE_APPEND` 的字符串值,这些值不能改。\n- 默认值必须是 `FINAL_MODE_PATCH_SEGMENT`。`replace_turn` 会替换本轮所有助手气泡,只能由发送方显式声明,不能改成默认值,也不能按启发式自动推断。\n- `annotate_chat_final` 和 `ensure_final_mode_inplace` 都不能覆盖已有的非空 `final_mode`,并且只处理 `event_type` 为空或为 chat.final 的 payload。\n- 两者的区别:`annotate_chat_final` 返回副本,并补上 `event_type`;`ensure_final_mode_inplace` 就地修改,不补 `event_type`。改动其中一个时,要检查两者是否仍然一致。\n\n<!-- kb:rule status=active since=init-f0a69728c96b -->\n"
},
"id": "init-bad-deepen-JIUWENSW-I15",
"note": "kb init deepen: judge said fail; on main the text is absent",
"rule_id": "JIUWENSW-I15",
"source": "judge",
"stage": "deepen",
"verdict": "fail"
}
Loading
Loading