feat: add guided exploration and share user-started MCP backend

This commit is contained in:
chenmingxuan committed 2026-10-16 18:32:46 +08:00
1 parent f86db1ced6
commit ee256ad7ce
144 files changed
+8625 -2075

No files matched your search

+156
View File
@@ -1119,6 +1119,34 @@ def test_history_filters_all_persisted_rows_and_escapes_like(client, isolated):
)
def test_history_returns_persisted_execution_mode_without_inventing_legacy_mode(client, isolated):
with isolated.connection() as connection:
for sid, device_info in [
("flash", '{"profile":"flash"}'),
("pro", '{"profile":"pro"}'),
("legacy", "not json"),
]:
connection.execute(
"INSERT INTO sessions(session_id,initial_goal,start_time,status,device_info) VALUES (?,?,?,?,?)",
(sid, sid, 100, "completed", device_info),
)
connection.commit()
isolated.save_job(
{
"session_id": "queued-only",
"status": "failed",
"case": {"name": "no device"},
"task_config": {"profile": "pro"},
"created_at": 100,
}
)
rows = {row["session_id"]: row for row in client.get("/api/automation/history").json()["items"]}
assert rows["flash"]["profile"] == "flash"
assert rows["pro"]["profile"] == "pro"
assert rows["legacy"]["profile"] is None
assert rows["queued-only"]["profile"] == "pro"
def test_batch_submission_is_atomic_and_idempotent(client, isolated):
value = payload(request_id="same-request")
first = client.post("/api/automation/batches", json=value)
@@ -1145,3 +1173,131 @@ def test_removed_replay_is_inert_but_history_survives(client, isolated):
assert client.post(f"/api/automation/batches/{batch['id']}/retry_failed").status_code == 409
assert client.get("/api/automation/workflows").status_code == 404
assert client.get("/api/automation/history").json()["total"] == 2
def test_exploration_history_groups_before_pagination_and_preserves_validations(client, isolated):
with isolated.connection() as conn:
for i, kind in enumerate(["exploration", "exploration", "validation", "validation"]):
sid = f"run-{i}"
conn.execute(
"INSERT INTO sessions(session_id,initial_goal,start_time,status) VALUES (?,?,?,?)",
(sid, "目标", i + 1, "completed"),
)
record = {
"exploration_id": "conversation",
"kind": kind,
"created_at": i + 1,
"status": "completed",
"case_version": 1,
}
conn.execute(
"INSERT INTO automation_records VALUES (?,?,?,?)",
("exploration_run", sid, json.dumps(record), i + 1),
)
conn.commit()
all_rows = client.get("/api/automation/history").json()
assert all_rows["total"] == 3
assert {r["session_id"] for r in all_rows["items"]} == {"run-1", "run-2", "run-3"}
page = client.get("/api/automation/history?batch_id=__exploration__&size=1").json()
assert page["total"] == 1
assert page["items"][0]["session_id"] == "run-1"
assert page["items"][0]["exploration_run_count"] == 2
ordinary = client.get("/api/automation/history?batch_id=__single__").json()
assert ordinary["total"] == 2
with isolated.connection() as conn:
conn.execute(
"INSERT INTO automation_records VALUES (?,?,?,?)",
(
"exploration",
"conversation",
json.dumps({"status": "paused", "active_run_id": None}),
5,
),
)
conn.commit()
assert client.get("/api/automation/history?batch_id=__exploration__").json()["total"] == 0
def test_validation_history_uses_case_title_after_loading_its_job(client, isolated):
from artemis.config.exploration import ExplorationConfig
from artemis.exploration.models import CaseDraft
from artemis.exploration.store import ExplorationStore
repository = ExplorationStore(isolated)
session = repository.create("探索设置", ExplorationConfig(), {}, "create")
repository.save_case(
session["id"], CaseDraft(name="深色主题检查", goal="完整执行提示词"), 0, "save"
)
run = repository.reserve_run(
session["id"],
"validate",
kind="validation",
goal="完整执行提示词",
task_config={},
case_version=1,
)
isolated.save_job(
{
"session_id": run["id"],
"status": "completed",
"name": "完整执行提示词",
"goal": "完整执行提示词",
}
)
with isolated.connection() as conn:
conn.execute(
"INSERT INTO sessions(session_id,initial_goal,start_time,status) VALUES (?,?,?,?)",
(run["id"], "完整执行提示词", 1, "completed"),
)
conn.commit()
rows = client.get("/api/automation/history?q=深色主题检查").json()["items"]
assert len(rows) == 1
assert rows[0]["name"] == "深色主题检查"
assert rows[0]["initial_goal"] == "完整执行提示词"
assert rows[0]["case_version"] == 1
@pytest.mark.asyncio
async def test_queued_exploration_keeps_identity_without_a_session_row(isolated, monkeypatch):
from apps.admin_console.routers import tasks
from artemis.exploration.store import ExplorationStore
from artemis.exploration.models import CreateExploration
from apps.admin_console.services.exploration_service import ExplorationService
service = ExplorationService(ExplorationStore(isolated))
session = service.create(CreateExploration(goal="排队探索", request_id="create"))
run = service.repository.reserve_run(
session["id"],
"start",
kind="exploration",
goal="排队探索",
task_config={"model": "gpt-6.1-sol"},
)
queue_item = {"session_id": run["id"], "goal": run["goal"], "status": "pending"}
from types import SimpleNamespace
monkeypatch.setattr(
tasks,
"state",
SimpleNamespace(
is_running=False,
queue_items=[],
queue_tasks=[queue_item],
active_runs={},
active_connections={},
active_session_id=None,
current_goal=None,
current_profile=None,
ipc_port=0,
),
)
monkeypatch.setattr(tasks, "store", isolated)
monkeypatch.setattr(tasks.task_queue_service, "ensure_worker_running", lambda: None)
monkeypatch.setattr(tasks, "session_repo", Mock(get_latest_session=lambda: None))
monkeypatch.setattr(tasks.DeviceExecutionLock, "get_active_owner", lambda: None)
monkeypatch.setattr(tasks.DeviceExecutionLock, "get_active_owners", lambda: {})
monkeypatch.setattr(tasks.DeviceExecutionLock, "get_queued_tasks", lambda: [])
result = await tasks.get_status()
assert result["queue"][0]["exploration_id"] == session["id"]
assert result["queue"][0]["exploration_kind"] == "exploration"
assert "exploration_id" not in queue_item # Do not mutate scheduler state.