From 6c4eac699f9596f196c460e3c5fe7ba5de39d1e4 Mon Sep 17 00:00:00 2001 From: MacMini2 Date: Mon, 3 Aug 2026 14:34:38 +0800 Subject: [PATCH] feat(client): expose browser policy evidence --- CHANGELOG.md | 3 +++ contracts/core-client.v1.json | 6 +++--- src/openlinker/types.py | 5 +++++ tests/test_client.py | 9 +++++++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8c29ec..458b04d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ This is a pre-1.0 breaking Runtime cutover. +- Added Browser interaction policy, policy generation, canonical mutation + origins, origin digest, and Browser contract evidence to `RunResponse` and + the public Core client contract fixture. - Added the async, single-use `RuntimeWorker` with direct Python handler execution. - Added credential-free Runtime discovery, mTLS, Session attachment generations, WebSocket/long-poll recovery, lease renewal, resume, cancellation and drain. diff --git a/contracts/core-client.v1.json b/contracts/core-client.v1.json index ebc1359..bf49773 100644 --- a/contracts/core-client.v1.json +++ b/contracts/core-client.v1.json @@ -21,9 +21,9 @@ {"client_method":"get_agent","http_method":"GET","path":"/api/v1/agents/{slug}","auth":"none"}, {"client_method":"get_agent_card","http_method":"GET","path":"/api/v1/agents/{slug}/agent-card.json","auth":"none"}, {"client_method":"get_agent_card","http_method":"GET","path":"/api/v1/agents/{slug}/agent-card.extended.json","auth":"none"}, - {"client_method":"run_agent","http_method":"POST","path":"/api/v1/run","auth":"access_token","required_scope":"agents:run","required_headers":["Idempotency-Key"],"success_statuses":[200,201,202],"response_headers":["Location","Idempotency-Replayed"],"response_fields":["agent_connection_mode","runtime_transport","runtime_transport_reason","runtime_transport_changed_at","dispatch_state","attempt_count","max_attempts","started_at","replayed"]}, - {"client_method":"start_agent_run","http_method":"POST","path":"/api/v1/runs","auth":"access_token","required_scope":"agents:run","required_headers":["Idempotency-Key"],"success_statuses":[200,201,202],"response_headers":["Location","Idempotency-Replayed"],"response_fields":["agent_connection_mode","runtime_transport","runtime_transport_reason","runtime_transport_changed_at","dispatch_state","attempt_count","max_attempts","started_at","replayed"]}, - {"client_method":"get_run","http_method":"GET","path":"/api/v1/runs/{id}","auth":"access_token","required_scope":"runs:read","response_fields":["agent_connection_mode","runtime_transport","runtime_transport_reason","runtime_transport_changed_at","dispatch_state","attempt_count","max_attempts","started_at","replayed"]}, + {"client_method":"run_agent","http_method":"POST","path":"/api/v1/run","auth":"access_token","required_scope":"agents:run","required_headers":["Idempotency-Key"],"success_statuses":[200,201,202],"response_headers":["Location","Idempotency-Replayed"],"response_fields":["agent_connection_mode","runtime_transport","runtime_transport_reason","runtime_transport_changed_at","browser_interaction_policy","browser_interaction_policy_generation","browser_mutation_origins","browser_mutation_origins_sha256","browser_contract_id","dispatch_state","attempt_count","max_attempts","started_at","replayed"]}, + {"client_method":"start_agent_run","http_method":"POST","path":"/api/v1/runs","auth":"access_token","required_scope":"agents:run","required_headers":["Idempotency-Key"],"success_statuses":[200,201,202],"response_headers":["Location","Idempotency-Replayed"],"response_fields":["agent_connection_mode","runtime_transport","runtime_transport_reason","runtime_transport_changed_at","browser_interaction_policy","browser_interaction_policy_generation","browser_mutation_origins","browser_mutation_origins_sha256","browser_contract_id","dispatch_state","attempt_count","max_attempts","started_at","replayed"]}, + {"client_method":"get_run","http_method":"GET","path":"/api/v1/runs/{id}","auth":"access_token","required_scope":"runs:read","response_fields":["agent_connection_mode","runtime_transport","runtime_transport_reason","runtime_transport_changed_at","browser_interaction_policy","browser_interaction_policy_generation","browser_mutation_origins","browser_mutation_origins_sha256","browser_contract_id","dispatch_state","attempt_count","max_attempts","started_at","replayed"]}, {"client_method":"list_run_events","http_method":"GET","path":"/api/v1/runs/{id}/events","auth":"access_token","required_scope":"runs:read","response_fields":["items","meta.requested_after_sequence","meta.effective_after_sequence","meta.retained_through_sequence","meta.earliest_available_sequence","meta.latest_available_sequence","meta.retention_gap","meta.terminal","meta.stream_complete"]}, {"client_method":"list_run_children","http_method":"GET","path":"/api/v1/runs/{id}/children","auth":"access_token","required_scope":"runs:read","response_fields":["parent_run_id","items[].child_run_id","items[].caller_agent_id","items[].target_agent_id","items[].status","items[].children"]}, {"client_method":"list_run_artifacts","http_method":"GET","path":"/api/v1/runs/{id}/artifacts","auth":"access_token","required_scope":"runs:read"}, diff --git a/src/openlinker/types.py b/src/openlinker/types.py index 3e812a8..ff1c8ce 100644 --- a/src/openlinker/types.py +++ b/src/openlinker/types.py @@ -206,6 +206,11 @@ class RunResponse(Model): runtime_transport: str | None = None runtime_transport_reason: str | None = None runtime_transport_changed_at: str | None = None + browser_interaction_policy: str | None = None + browser_interaction_policy_generation: int | None = None + browser_mutation_origins: list[str] = jfield(default_factory=list) + browser_mutation_origins_sha256: str | None = None + browser_contract_id: str | None = None dispatch_state: str = "" attempt_count: int = 0 max_attempts: int = 0 diff --git a/tests/test_client.py b/tests/test_client.py index 26c8dd9..c251342 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -71,6 +71,11 @@ async def handler(request: httpx.Request) -> httpx.Response: "runtime_transport": "long_poll", "runtime_transport_reason": "websocket_unavailable", "runtime_transport_changed_at": "2026-07-18T00:00:00Z", + "browser_interaction_policy": "full", + "browser_interaction_policy_generation": 7, + "browser_mutation_origins": ["https://example.com"], + "browser_mutation_origins_sha256": "a" * 64, + "browser_contract_id": "openlinker.browser.v2", "dispatch_state": "terminal", "attempt_count": 1, "max_attempts": 3, @@ -99,6 +104,10 @@ async def handler(request: httpx.Request) -> httpx.Response: assert resp.agent_connection_mode == "runtime" assert resp.runtime_transport == "long_poll" assert resp.runtime_transport_reason == "websocket_unavailable" + assert resp.browser_interaction_policy == "full" + assert resp.browser_interaction_policy_generation == 7 + assert resp.browser_mutation_origins == ["https://example.com"] + assert resp.browser_contract_id == "openlinker.browser.v2" assert resp.dispatch_state == "terminal" assert resp.attempt_count == 1 assert seen["path"] == "/api/v1/run"