diff --git a/examples/personal-workspace-browser/conversation-return-continuity.mjs b/examples/personal-workspace-browser/conversation-return-continuity.mjs index 1cca888b33..375d2c44e7 100644 --- a/examples/personal-workspace-browser/conversation-return-continuity.mjs +++ b/examples/personal-workspace-browser/conversation-return-continuity.mjs @@ -37,10 +37,17 @@ export const conversationReturnContinuityScenario = { if (api.turnRequests.at(-1).sessionId === oldSessionId) throw new Error("Fixture did not replace the active session"); const turnsBeforeReturn = api.turnRequests.length; let droppedRead = false; - await page.route(`**/api/chat/sessions/${oldSessionId}`, async (route) => { + const failedRead = page.waitForEvent("requestfailed", { + predicate: (request) => new URL(request.url()).pathname === `/api/chat/sessions/${oldSessionId}`, + timeout: 10_000, + }); + await page.route((url) => url.pathname === `/api/chat/sessions/${oldSessionId}`, async (route) => { if (!droppedRead) { droppedRead = true; await route.abort("connectionreset"); } else await route.fallback(); }); + // A streamed return can arrive before the next snapshot poll. Observe + // the injected read failure before publishing the late result. + await failedRead; const answer = "核对完成:现金流期间和单位一致。"; page.__loopxRuntime.messages.get(oldSessionId).push({ message_id: "handoff.old-result", turn_id: "old-turn", role: "agent", origin: "manager_followup", diff --git a/tests/control_plane/test_replan_progress_native_cli.py b/tests/control_plane/test_replan_progress_native_cli.py index 5b1869f1aa..751699eac0 100644 --- a/tests/control_plane/test_replan_progress_native_cli.py +++ b/tests/control_plane/test_replan_progress_native_cli.py @@ -115,6 +115,12 @@ def make_progress_cli_fixture(root: Path, provider: str, scenario: str) -> dict: def call_progress_cli(fixture: dict, *args: str, expected_code: int = 0) -> dict: + # These tests inspect the authority decision, independently of machine + # presentation defaults. Preserve explicit context delivery overrides. + if args[:2] == ("quota", "should-run") and not any( + arg == "--context-delivery" or arg.startswith("--context-delivery=") for arg in args + ): + args = (*args, "--context-delivery", "off") result = subprocess.run([ sys.executable, "-m", "loopx.cli", "--registry", str(fixture["registry"]), "--runtime-root", str(fixture["runtime"]), "--format", "json", *args, @@ -124,6 +130,40 @@ def call_progress_cli(fixture: dict, *args: str, expected_code: int = 0) -> dict return json.loads(result.stdout) +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +@pytest.mark.parametrize("context_args,navigation", [ + ((), False), + (("--context-delivery", "on"), True), + (("--context-delivery=on",), True), + (("--context-delivery", "off"), False), + (("--context-delivery=off",), False), +], ids=["omitted", "on-split", "on-equals", "off-split", "off-equals"]) +def test_progress_cli_context_delivery_overrides( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, provider: str, + context_args: tuple[str, ...], navigation: bool, +) -> None: + tmp_path = tmp_path.resolve() + isolate_sqlite_runtime(tmp_path, monkeypatch) + monkeypatch.setenv("LOOPX_CONTEXT_DELIVERY", "on") + native = tmp_path / "native" + native.mkdir(mode=0o700) + captures = tmp_path / "captures" + captures.mkdir(mode=0o700) + fixture = make_progress_cli_fixture(tmp_path, provider, "advanced") + try: + result = call_progress_cli( + fixture, "quota", "should-run", "--codex-app", "--goal-id", GOAL, + "--agent-id", AGENT, "--turn-instance-id", TURN, + "--codex-context-dir", str(native), + "--decision-output-root", str(captures), *context_args, + ) + assert (result.get("schema_version") == "context_capture_navigation_v1") is navigation + if not navigation: + assert result["decision"] == "autonomous_replan_required", result + finally: + restart_effect_runtime(wait_for_exit=True) + + def _authority(fixture: dict) -> dict: result = read_canonical_snapshot(rpc=effect_runtime_result, runtime_root=str(fixture["runtime"]), goal_id=GOAL, include_leases=True, projection_readback=None, timeout=20)