Files
omnigent-ai--omnigent/tests/test_sessions_native_messages.py
Serena Ruan 7efe05623b revert(sessions): unwind the #2150 approval/attribution stack (#3446, #3422, #3416) (#4318)
* revert(sessions): remove delegated approval authority (#3446)

Reverts the delegated approval feature from #3446, returning to
owner-only approval (the deny-by-default behavior from #3416). Owners
can no longer delegate a "can_approve" capability to shared editors;
approvals are again restricted to the session owner, while editors keep
reject/cancel.

The change is a faithful inverse of #3446 rebased on current main:
files untouched since #3446 revert byte-identical to their pre-feature
state; files later commits also modified keep those newer changes and
drop only the approval lines.

Migration handled non-destructively for deployed databases:
- The original additive migration (c4d5e6f7a8b9) is kept intact so
  already-migrated databases still resolve their history.
- A new forward migration (f7a8b9c0d1e2) drops the session_permissions
  .can_approve column; its downgrade re-adds it.

Also removes a dangling import of _approval_access_from_grants in
sessions/__init__.py left by the later wildcard-import refactor (#3934),
which otherwise broke server import after the helper was reverted.

Signed-off-by: Serena Ruan <serena.rxy@gmail.com>

* revert(sessions): remove shared-message attribution (#3422)

Reverts the model-visible shared-message authorship feature from #3422.
Messages no longer gain `[author]:` prefixes in the model prompt, the
SHARED_SESSION_AUTHORSHIP_INSTRUCTION framework instruction is removed,
and the OMNIGENT_SHARED_MESSAGE_ATTRIBUTION_ENABLED switch is gone.
Persisted `created_by` authorship (a store-level column predating #3422)
is unaffected.

Rebased on current main, keeping later independent work in the same
regions:
- Smart Routing's conditional `model_override` on the native-terminal
  forward path is preserved.
- The `host_store` parameter added to the event-forward path is kept.
- The two `test_external_interrupt_*` tests from #4160 (which overlap
  #3422's added block in test_sessions_endpoints.py) are kept; only
  #3422's `test_external_user_message_strips_model_author_prefix` is
  removed.

Also removes dangling imports of `_strip_pending_author_prefix` in
orchestration.py and sessions/__init__.py left after the helper's
definition was reverted.

Signed-off-by: Serena Ruan <serena.rxy@gmail.com>

* revert(sessions): restore editor approval authority (#3416)

Reverts the owner-only approval restriction from #3416. Approval events
and URL-based elicitation resolution are gated at LEVEL_EDIT again, so
shared editors — not only the owner — can resolve approvals.

SECURITY REGRESSION (intentional, per request): #3416 was a security
fix. Shared-session tools execute with the session owner's runner
identity and ambient credentials, so a shared editor can once more
authorize owner-credentialed tool calls. This, together with the #3422
and #3446 reverts, fully unwinds the #2150 stack and re-opens #2150.

Signed-off-by: Serena Ruan <serena.rxy@gmail.com>

---------

Signed-off-by: Serena Ruan <serena.rxy@gmail.com>
2026-08-07 12:54:05 +08:00

263 lines
10 KiB
Python

"""Tests for native terminal message dispatch helpers."""
from __future__ import annotations
import httpx
import pytest
from omnigent.entities.conversation import Conversation
from omnigent.server.schemas import SessionEventInput
def _conversation_with_wrapper(wrapper: str) -> Conversation:
"""
Build a conversation row carrying one wrapper label.
:param wrapper: Wrapper label value, e.g. ``"codex-native-ui"``.
:returns: Conversation with that label and a bound agent_id.
"""
return Conversation(
id="e1f7c651c9f97fac088ea70ef633409d",
created_at=0,
updated_at=0,
root_conversation_id="e1f7c651c9f97fac088ea70ef633409d",
agent_id="d5de5cef9504e12d06e729f3071d4f48",
labels={"omnigent.wrapper": wrapper},
)
def _message_event() -> SessionEventInput:
"""
Build one user message event for native dispatch tests.
:returns: Sessions API message input.
"""
return SessionEventInput(
type="message",
data={
"role": "user",
"content": [{"type": "input_text", "text": "hello"}],
},
)
def test_codex_native_session_uses_codex_harness_for_web_messages() -> None:
"""
Codex-native sessions use the native bypass and dispatch web
messages into the ``codex-native`` harness instead of the normal
Omnigent persistence path.
"""
from omnigent.server.routes import sessions as sessions_routes
conv = _conversation_with_wrapper("codex-native-ui")
assert sessions_routes._is_native_terminal_session(conv) is True
# agent_id must be forwarded so the runner can resolve the harness
# spec on the first message, before POST /v1/sessions caches it —
# otherwise the turn falls back to "runner-test-default" and drops.
assert sessions_routes._build_native_terminal_message_event(conv, _message_event()) == {
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": "hello"}],
"model": "codex-native-ui",
"harness": "codex-native",
"agent_id": "d5de5cef9504e12d06e729f3071d4f48",
}
def test_kiro_native_session_uses_kiro_harness_for_web_messages() -> None:
"""Kiro-native web messages use the native bypass, like Codex."""
from omnigent.server.routes import sessions as sessions_routes
conv = _conversation_with_wrapper("kiro-native-ui")
assert sessions_routes._is_native_terminal_session(conv) is True
assert sessions_routes._build_native_terminal_message_event(conv, _message_event()) == {
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": "hello"}],
"model": "kiro-native-ui",
"harness": "kiro-native",
"agent_id": "d5de5cef9504e12d06e729f3071d4f48",
}
def test_antigravity_native_session_uses_antigravity_harness_for_web_messages() -> None:
"""
Antigravity-native sessions use the native bypass and dispatch web
messages into the ``antigravity-native`` harness, mirroring the
codex/claude native-terminal wrappers. Without this the web UI would
persist the message itself instead of forwarding it to the agy terminal,
and the runner would never see the turn.
"""
from omnigent.server.routes import sessions as sessions_routes
conv = _conversation_with_wrapper("antigravity-native-ui")
assert sessions_routes._is_native_terminal_session(conv) is True
assert sessions_routes._build_native_terminal_message_event(conv, _message_event()) == {
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": "hello"}],
"model": "antigravity-native-ui",
"harness": "antigravity-native",
"agent_id": "d5de5cef9504e12d06e729f3071d4f48",
}
def test_antigravity_native_runtime_maps_wrapper_to_agy_terminal() -> None:
"""
The wrapper label resolves to the agy display name, model, harness, and
the ``antigravity`` runner terminal resource name. The ensure-readiness
probe (``_ensure_native_terminal_ready``) routes off exactly these two
helpers, so a missing antigravity branch would 400 the first web message.
"""
from omnigent.server.routes import sessions as sessions_routes
conv = _conversation_with_wrapper("antigravity-native-ui")
display_name, model, harness = sessions_routes._native_terminal_runtime(conv)
assert (display_name, model, harness) == (
"Antigravity",
"antigravity-native-ui",
"antigravity-native",
)
assert sessions_routes._native_terminal_name_for_harness(harness) == "antigravity"
def test_transcript_forwarded_native_sessions_use_native_bypass() -> None:
"""Transcript-forwarded native sessions skip AP-side message persistence."""
from omnigent.server.routes import sessions as sessions_routes
assert sessions_routes._is_native_terminal_session(
_conversation_with_wrapper("claude-code-native-ui")
)
assert sessions_routes._is_native_terminal_session(
_conversation_with_wrapper("codex-native-ui")
)
assert sessions_routes._is_native_terminal_session(
_conversation_with_wrapper("kiro-native-ui")
)
def test_unknown_wrapper_session_does_not_use_native_bypass() -> None:
"""
Non-native wrapper labels must not enter the native terminal
bypass, otherwise Omnigent would skip persistence for regular sessions.
"""
from omnigent.server.routes import sessions as sessions_routes
conv = _conversation_with_wrapper("regular-chat")
assert sessions_routes._is_native_terminal_session(conv) is False
@pytest.mark.parametrize(
"response,expected",
[
# Runner attached a degrade reason → it becomes the banner notice.
(
httpx.Response(200, json={"policy_hook_disabled_reason": "codex too old"}),
"codex too old",
),
# Healthy session: no key → no notice (enforcement active).
(httpx.Response(200, json={"resource": "view"}), None),
# Whitespace-only reason is treated as absent (would fail ErrorData).
(httpx.Response(200, json={"policy_hook_disabled_reason": " "}), None),
# Non-dict body (defensive) → no notice.
(httpx.Response(200, json=["not", "a", "dict"]), None),
# Non-JSON 2xx body must not crash the readiness probe.
(httpx.Response(200, text="<<not json>>"), None),
],
)
def test_policy_notice_from_ensure_response(
response: httpx.Response, expected: str | None
) -> None:
"""
The ensure-response parser fires a banner only for a real reason.
This gate decides whether a non-fatal "policy not enforced" banner is
posted. It must return the reason verbatim when present, and ``None``
(no banner) for a healthy session, a blank reason, a non-dict body, or
a non-JSON 2xx body — the last of which must not turn a successful
readiness probe into a crash.
"""
from omnigent.server.routes import sessions as sessions_routes
assert sessions_routes._policy_notice_from_ensure_response(response) == expected
# ── native routing is harness-driven, not presentation-driven ────────
def test_custom_native_harness_session_without_wrapper_label_is_native(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""A chat-first custom agent on a native harness is still single-writer.
A user agent that declares ``executor.harness: codex-native`` but is not a
built-in ``*-native-ui`` wrapper (e.g. a ``polly`` orchestrator) carries NO
``omnigent.wrapper`` label — it renders chat-first on purpose. Its runner
still runs a native transcript forwarder, so the persist decision must
treat it as native via the RESOLVED harness; otherwise the inbound user
message is persisted AP-side AND mirrored by the forwarder (double input).
"""
from omnigent.server.routes import sessions as sessions_routes
conv = Conversation(
id="0e877e3fab4a2d5f5e386ef9f791eec0",
created_at=0,
updated_at=0,
root_conversation_id="0e877e3fab4a2d5f5e386ef9f791eec0",
agent_id="61fc939de6af22c5349fa22ba6e62aca",
labels={}, # chat-first: no wrapper / ui presentation labels
)
monkeypatch.setattr(sessions_routes, "_resolve_harness", lambda _c: "codex-native")
assert sessions_routes._is_native_terminal_session(conv) is True
# The native dispatch branch resolves runtime strings from the SAME
# resolver, so a label-less native session no longer raises
# "Unsupported native terminal session".
display_name, _model, harness = sessions_routes._native_terminal_runtime(conv)
assert (display_name, harness) == ("Codex", "codex-native")
def test_custom_sdk_harness_session_is_not_native(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""An SDK-harness session keeps the normal persist-before-forward path.
SDK harnesses have no transcript forwarder, so the server's single
persisted copy is correct — the harness fallback must not over-fire.
"""
from omnigent.server.routes import sessions as sessions_routes
conv = Conversation(
id="9842b654446e37e810871eba75f58608",
created_at=0,
updated_at=0,
root_conversation_id="9842b654446e37e810871eba75f58608",
agent_id="112e3284aa0a61b1b971de591fae1a26",
labels={},
)
monkeypatch.setattr(sessions_routes, "_resolve_harness", lambda _c: "claude-sdk")
assert sessions_routes._is_native_terminal_session(conv) is False
def test_wrapper_label_session_is_native_without_resolving_harness(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The wrapper-label path short-circuits before the harness fallback.
Built-in terminal-first wrapper sessions are recognized by label alone, so
the (spec-loading) harness resolution never runs for them.
"""
from omnigent.server.routes import sessions as sessions_routes
conv = _conversation_with_wrapper("codex-native-ui")
def _must_not_run(_c: object) -> str:
raise AssertionError("harness resolution must not run when the wrapper label matches")
monkeypatch.setattr(sessions_routes, "_resolve_harness", _must_not_run)
assert sessions_routes._is_native_terminal_session(conv) is True