mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-17 02:03:25 +08:00
feat: plan agent v2, global chat mode, and user-mode v2 flag sync
- Add runtime/plan_agent_v2 package and shims; gateway/direct_loop/WS wiring - Admin chat: interaction mode and specialist only in user menu; session API stores memory_mode and execution_mode only - POST /admin/api/chat/user-mode mirrors plan_agent_version to AIA_EXPERT_PLAN_AGENT_V2_ENABLED (v2 to 1, v1 to 0) - Composer cleanup (hidden mode select, no reasoning toggle in meta bar); tests and _local/system.env.example Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
ae44cbcad5
commit
37522f492a
49 changed files with 4146 additions and 238 deletions
142
tests/test_plan_agent_v2_gateway_dryrun.py
Normal file
142
tests/test_plan_agent_v2_gateway_dryrun.py
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from oclaw.platform.persistence.sqlite_store import SqliteStore
|
||||
from oclaw.runtime.gateway import OclawGatewayResult
|
||||
from oclaw.runtime.plan_agent_v2 import (
|
||||
build_shadow_gateway_result,
|
||||
evaluate_gateway_expert_turn_shadow,
|
||||
legacy_gateway_result_keys,
|
||||
)
|
||||
from oclaw.runtime.types import StandardMessage
|
||||
|
||||
|
||||
def _msg(text: str) -> StandardMessage:
|
||||
return StandardMessage(
|
||||
session_id="sess-dryrun",
|
||||
tenant_id="tenant-1",
|
||||
user_id="user-1",
|
||||
role="user",
|
||||
channel="chat",
|
||||
text=text,
|
||||
attachments=[],
|
||||
metadata={},
|
||||
)
|
||||
|
||||
|
||||
def test_gateway_shadow_stays_off_without_force_or_flag(tmp_path: Path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "ops.sqlite"))
|
||||
out = evaluate_gateway_expert_turn_shadow(
|
||||
store=store,
|
||||
msg=_msg("实现一个功能"),
|
||||
lang="zh",
|
||||
interaction_mode="expert",
|
||||
requested_specialist="generalist",
|
||||
base_system_prompt="base-system",
|
||||
force_flag=False,
|
||||
)
|
||||
assert out.used_v2 is False
|
||||
assert out.decision is None
|
||||
|
||||
|
||||
def test_gateway_shadow_force_path_matches_legacy_shape(tmp_path: Path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "ops.sqlite"))
|
||||
store.set_setting("AIA_EXPERT_PLAN_FILE_DIR", str(tmp_path / "plans"))
|
||||
out = evaluate_gateway_expert_turn_shadow(
|
||||
store=store,
|
||||
msg=_msg("请先给计划"),
|
||||
lang="zh",
|
||||
interaction_mode="expert",
|
||||
requested_specialist="generalist",
|
||||
base_system_prompt="base-system",
|
||||
force_flag=True,
|
||||
)
|
||||
assert out.used_v2 is True
|
||||
assert out.decision is not None
|
||||
|
||||
shadow_row = build_shadow_gateway_result(
|
||||
decision=out.decision,
|
||||
run_id="run-1",
|
||||
trace_id="trace-1",
|
||||
elapsed_ms=9,
|
||||
requested_specialist="generalist",
|
||||
)
|
||||
assert set(shadow_row.keys()) == legacy_gateway_result_keys()
|
||||
|
||||
baseline = OclawGatewayResult(run_id="run-1", reply_text="", trace_id="trace-1", elapsed_ms=9)
|
||||
assert shadow_row["mode"] == baseline.mode
|
||||
assert shadow_row["task_id"] == baseline.task_id
|
||||
assert shadow_row["dynamic_agent_used"] == baseline.dynamic_agent_used
|
||||
assert shadow_row["relay_pointer_count"] == baseline.relay_pointer_count
|
||||
|
||||
|
||||
def test_gateway_shadow_confirm_path_builds_compatible_result(tmp_path: Path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "ops.sqlite"))
|
||||
store.set_setting("AIA_EXPERT_PLAN_FILE_DIR", str(tmp_path / "plans"))
|
||||
|
||||
# Enter plan
|
||||
first = evaluate_gateway_expert_turn_shadow(
|
||||
store=store,
|
||||
msg=_msg("我要改造一下"),
|
||||
lang="zh",
|
||||
interaction_mode="expert",
|
||||
requested_specialist="generalist",
|
||||
base_system_prompt="base-system",
|
||||
force_flag=True,
|
||||
)
|
||||
assert first.used_v2 is True
|
||||
assert first.decision is not None
|
||||
assert first.decision.action == "run_agent"
|
||||
assert "base-system" in str(first.decision.system_prompt_override or "")
|
||||
|
||||
# Confirm plan
|
||||
second = evaluate_gateway_expert_turn_shadow(
|
||||
store=store,
|
||||
msg=_msg("确认"),
|
||||
lang="zh",
|
||||
interaction_mode="expert",
|
||||
requested_specialist="generalist",
|
||||
base_system_prompt="base-system",
|
||||
force_flag=True,
|
||||
)
|
||||
assert second.used_v2 is True
|
||||
assert second.decision is not None
|
||||
assert second.decision.action == "stay_plan"
|
||||
assert "切换到 agent 模式" in str(second.decision.reply_text or "")
|
||||
|
||||
row = build_shadow_gateway_result(
|
||||
decision=second.decision,
|
||||
run_id="run-2",
|
||||
trace_id="trace-2",
|
||||
elapsed_ms=12,
|
||||
requested_specialist="generalist",
|
||||
)
|
||||
assert row["interaction_mode"] == "expert"
|
||||
assert str(row["dispatch_reason"]).startswith("plan_agent_v2:")
|
||||
|
||||
|
||||
def test_gateway_shadow_skips_v2_when_metadata_plan_agent_version_v1(tmp_path: Path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "ops.sqlite"))
|
||||
store.set_setting("AIA_EXPERT_PLAN_FILE_DIR", str(tmp_path / "plans"))
|
||||
out = evaluate_gateway_expert_turn_shadow(
|
||||
store=store,
|
||||
msg=StandardMessage(
|
||||
session_id="sess-dryrun",
|
||||
tenant_id="tenant-1",
|
||||
user_id="user-1",
|
||||
role="user",
|
||||
channel="chat",
|
||||
text="请先给计划",
|
||||
attachments=[],
|
||||
metadata={"plan_agent_version": "v1"},
|
||||
),
|
||||
lang="zh",
|
||||
interaction_mode="expert",
|
||||
requested_specialist="generalist",
|
||||
base_system_prompt="base-system",
|
||||
force_flag=True,
|
||||
)
|
||||
assert out.used_v2 is False
|
||||
assert out.decision is None
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue