feat: integrate production cockpit AI assistant
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import tomllib
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
@@ -11,6 +12,7 @@ import pytest
|
||||
from cognitive_agent.infrastructure.codex.app_server import (
|
||||
CodexAppServerClient,
|
||||
CodexAppServerProtocolError,
|
||||
CodexAppServerTimeout,
|
||||
CodexAppServerUnavailable,
|
||||
CodexTurnHandle,
|
||||
)
|
||||
@@ -47,6 +49,21 @@ def _events_until_completed(handle: CodexTurnHandle) -> list[dict[str, object]]:
|
||||
return events
|
||||
|
||||
|
||||
def _wait_for_wire_method(trace_path: Path, method: str) -> list[dict[str, object]]:
|
||||
deadline = time.monotonic() + 2
|
||||
messages: list[dict[str, object]] = []
|
||||
while time.monotonic() < deadline:
|
||||
if trace_path.exists():
|
||||
messages = [
|
||||
json.loads(line)
|
||||
for line in trace_path.read_text(encoding="utf-8").splitlines()
|
||||
]
|
||||
if any(message.get("method") == method for message in messages):
|
||||
return messages
|
||||
time.sleep(0.01)
|
||||
raise AssertionError(f"timed out waiting for {method}: {messages}")
|
||||
|
||||
|
||||
def test_real_stdio_adapter_handshake_stream_and_transport_overrides(tmp_path: Path) -> None:
|
||||
client, trace_path = _client(tmp_path)
|
||||
try:
|
||||
@@ -124,6 +141,102 @@ def test_interrupt_is_sent_and_waits_for_authoritative_terminal_event(tmp_path:
|
||||
assert terminal["status"] == "interrupted"
|
||||
|
||||
|
||||
def test_late_thread_start_response_is_unsubscribed_after_rpc_timeout(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
client, trace_path = _client(tmp_path)
|
||||
try:
|
||||
client.start(CALLBACK_URL, CALLBACK_TOKEN)
|
||||
with pytest.raises(CodexAppServerTimeout):
|
||||
client.start_thread(
|
||||
developer_instructions="DELAY_THREAD_START",
|
||||
ephemeral=True,
|
||||
timeout=0.02,
|
||||
)
|
||||
wire = _wait_for_wire_method(trace_path, "thread/unsubscribe")
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
start = next(item for item in wire if item.get("method") == "thread/start")
|
||||
unsubscribe = next(
|
||||
item for item in wire if item.get("method") == "thread/unsubscribe"
|
||||
)
|
||||
assert start["id"] != unsubscribe["id"]
|
||||
assert unsubscribe["params"] == {"threadId": "thread-1"}
|
||||
|
||||
|
||||
def test_late_structured_turn_response_is_interrupted_without_backlog_leak(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
client, trace_path = _client(tmp_path)
|
||||
try:
|
||||
client.start(CALLBACK_URL, CALLBACK_TOKEN)
|
||||
thread_id = client.start_thread(developer_instructions="Test", ephemeral=True)
|
||||
with pytest.raises(CodexAppServerTimeout):
|
||||
client.start_structured_turn(
|
||||
thread_id,
|
||||
"DELAY_TURN_START",
|
||||
application_context={"capabilities": []},
|
||||
output_schema={
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"required": [],
|
||||
"properties": {},
|
||||
},
|
||||
timeout=0.02,
|
||||
)
|
||||
wire = _wait_for_wire_method(trace_path, "turn/interrupt")
|
||||
_wait_for_wire_method(trace_path, "thread/unsubscribe")
|
||||
deadline = time.monotonic() + 1
|
||||
while time.monotonic() < deadline:
|
||||
with client._lock:
|
||||
if not client._turns and not client._event_backlog:
|
||||
break
|
||||
time.sleep(0.01)
|
||||
with client._lock:
|
||||
assert client._turns == {}
|
||||
assert client._event_backlog == {}
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
interrupt = next(item for item in wire if item.get("method") == "turn/interrupt")
|
||||
assert interrupt["params"] == {"threadId": thread_id, "turnId": "turn-1"}
|
||||
|
||||
|
||||
def test_abandon_turn_releases_handle_and_discards_all_late_events(tmp_path: Path) -> None:
|
||||
client, _trace_path = _client(tmp_path)
|
||||
handle = CodexTurnHandle(thread_id="thread-abandoned", turn_id="turn-abandoned")
|
||||
key = (handle.thread_id, handle.turn_id)
|
||||
with client._lock:
|
||||
client._turns[key] = handle
|
||||
|
||||
client.abandon_turn(handle)
|
||||
client._dispatch(
|
||||
{
|
||||
"method": "item/agentMessage/delta",
|
||||
"params": {
|
||||
"threadId": handle.thread_id,
|
||||
"turnId": handle.turn_id,
|
||||
"delta": "late",
|
||||
},
|
||||
}
|
||||
)
|
||||
client._dispatch(
|
||||
{
|
||||
"method": "turn/completed",
|
||||
"params": {
|
||||
"threadId": handle.thread_id,
|
||||
"turn": {"id": handle.turn_id, "status": "interrupted"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
with client._lock:
|
||||
assert key not in client._turns
|
||||
assert key not in client._event_backlog
|
||||
assert key in client._abandoned_turns
|
||||
|
||||
|
||||
def test_startup_bootstraps_missing_builtin_account_from_api_key_without_leaking_it(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
@@ -642,15 +755,22 @@ def test_client_can_restart_with_a_fresh_isolated_workspace(
|
||||
)
|
||||
first_workspace = client._cwd
|
||||
client.start(CALLBACK_URL, CALLBACK_TOKEN)
|
||||
first_cleanup_executor = client._late_cleanup_executor
|
||||
assert first_cleanup_executor is not None
|
||||
assert first_cleanup_executor._max_workers == 4
|
||||
client.close()
|
||||
|
||||
assert not first_workspace.exists()
|
||||
assert client._late_cleanup_executor is None
|
||||
client.start(CALLBACK_URL, CALLBACK_TOKEN)
|
||||
second_workspace = client._cwd
|
||||
try:
|
||||
assert client.available is True
|
||||
assert second_workspace != first_workspace
|
||||
assert second_workspace.is_dir()
|
||||
assert client._late_cleanup_executor is not first_cleanup_executor
|
||||
assert client._late_cleanup_executor is not None
|
||||
assert client._late_cleanup_executor._max_workers == 4
|
||||
finally:
|
||||
client.close()
|
||||
assert not second_workspace.exists()
|
||||
|
||||
Reference in New Issue
Block a user